在Groovy中从List创建Map的快捷方式是什么?

122

我希望这里有一个简称:

Map rowToMap(row) {
    def rowMap = [:];
    row.columns.each{ rowMap[it.name] = it.val }
    return rowMap;
}

鉴于GDK的设计,我期望能够做类似以下的事情:

Map rowToMap(row) {
    row.columns.collectMap{ [it.name,it.val] }
}

但我在文档中没有看到任何东西... 我是漏了什么吗?还是我太懒了?


2
Amir的评论现在是正确的答案:https://dev59.com/snVD5IYBdhLWcg3wU56H#4484958 - Robert Fischer
8个回答

146

我最近遇到了这个需求:将一个列表转换成一个映射表。这个问题是在 Groovy 版本 1.7.9 发布之前发布的,所以方法 collectEntries 还不存在。它的用法与 collectMap 方法完全相同,后者是通过提议实现的:

Map rowToMap(row) {
    row.columns.collectEntries{[it.name, it.val]}
}

如果因某些原因您被困在旧版本的Groovy中,也可以使用inject方法(如此处所提出的)。这是一个略微修改的版本,仅在闭包内部采用一个表达式(只为了节省字符!):

Map rowToMap(row) {
    row.columns.inject([:]) {map, col -> map << [(col.name): col.val]}
}

使用+操作符也可以代替<<


31

看看 "inject"。真正的函数式编程专家称其为 "fold"。

columns.inject([:]) { memo, entry ->
    memo[entry.name] = entry.val
    return memo
}

另外,当你这样做的时候,你可能希望将方法定义为类别,而不是直接在元类上定义。这样,你就可以将其定义一次,适用于所有集合:

class PropertyMapCategory {
    static Map mapProperty(Collection c, String keyParam, String valParam) {
        return c.inject([:]) { memo, entry ->
            memo[entry[keyParam]] = entry[valParam]
            return memo
        }
    }
}

示例用法:

use(PropertyMapCategory) {
    println columns.mapProperty('name', 'val')
}

我猜在Groovy中它被称为inject,可能是受Smalltalk中的inject:into:启发而来:| list sum | list:= OrderedCollection new add:1; add:2; add:3; yourself. sum:= list inject:0 into:[:a :b | a + b]。 Transcript cr; show:sum。"prints 6" - OlliP

13

在此问题被提出时,groupBy 方法是否不可用?


似乎不是,自2011年1.8.1版本以来就已经有了。这个问题是在2008年提出的。 但无论如何,groupBy现在确实是前进的方式。 - mvmn
正如您在groupBy的文档中所看到的,它基本上将元素分组成组,其中每个组包含与某个键匹配的元素。因此,它的返回类型是Map<K,List<V>>。看起来OP正在寻找一个返回类型为Map<K,V>的方法,因此在这种情况下groupBy不起作用。 - Krzysiek Przygudzki

11
如果您所需的是一个简单的键值对,那么方法collectEntries应该就可以满足要求。例如:
def names = ['Foo', 'Bar']
def firstAlphabetVsName = names.collectEntries {[it.charAt(0), it]} // [F:Foo, B:Bar]

但如果你想要一个类似于Multimap的结构,其中每个键有多个值,则应该使用groupBy方法。

def names = ['Foo', 'Bar', 'Fooey']
def firstAlphabetVsNames = names.groupBy { it.charAt(0) } // [F:[Foo, Fooey], B:[Bar]]

6

5

好的...我已经尝试过这个方法,我认为这是一个相当不错的方法...

def collectMap = {Closure callback->
    def map = [:]
    delegate.each {
        def r = callback.call(it)
        map[r[0]] = r[1]
    }
    return map
}
ExpandoMetaClass.enableGlobally()
Collection.metaClass.collectMap = collectMap
Map.metaClass.collectMap = collectMap

现在,任何Map或Collection的子类都有这个方法...

在这里,我使用它来反转Map中的键/值对。

[1:2, 3:4].collectMap{[it.value, it.key]} == [2:1, 4:3]

这里我使用它来从列表中创建地图

[1,2].collectMap{[it,it]} == [1:1, 2:2]

现在,我只需要将这个代码块放入一个类中,在我的应用程序启动时调用该类即可。然后,这个方法就可以在我的代码中随处使用。
编辑:
要将此方法添加到所有数组中...
Object[].metaClass.collectMap = collectMap

1

我找不到任何内置的东西...但是使用ExpandoMetaClass,我可以做到这一点:

ArrayList.metaClass.collectMap = {Closure callback->
    def map = [:]
    delegate.each {
        def r = callback.call(it)
        map[r[0]] = r[1]
    }
    return map
}

这将为所有ArrayList添加collectMap方法...我不确定为什么将其添加到List或Collection中没有起作用...我想那是另一个问题了...但现在我可以这样做...

assert ["foo":"oof", "42":"24", "bar":"rab"] ==
            ["foo", "42", "bar"].collectMap { return [it, it.reverse()] }

从列表到使用一个闭包计算的映射...这正是我正在寻找的。

编辑:我不能将该方法添加到接口列表和集合中的原因是因为我没有做到这一点:

List.metaClass.enableGlobally()

在调用该方法之后,您可以向接口添加方法。在这种情况下,这意味着我的collectMap方法将适用于这样的范围:

(0..2).collectMap{[it, it*2]}

这将产生映射:[0:0, 1:2, 2:4]


0

这样的东西怎么样?

// setup
class Pair { 
    String k; 
    String v; 
    public Pair(def k, def v) { this.k = k ; this.v = v; }
}
def list = [ new Pair('a', 'b'), new Pair('c', 'd') ]

// the idea
def map = [:]
list.each{ it -> map.putAt(it.k, it.v) }

// verify
println map['c']

基本上和我的问题一样...我只是用了map[it.k] = it.v而不是.putAt我在寻找一个一行代码的解决方案。 - danb

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接