ярлык для создания карты из списка в Groovy?

Я хотел бы что-то вроде этого:

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] }
}

но я ничего не видел в документах ... я что-то упустил? или я просто слишком ленив?

20.08.2008 18:37:51
Комментарий Амира теперь является правильным ответом: stackoverflow.com/a/4484958/27561
Robert Fischer 24.05.2015 18:01:16
8 ОТВЕТОВ
РЕШЕНИЕ

Недавно я столкнулся с необходимостью сделать именно это: преобразовать список в карту. Этот вопрос был опубликован до выхода версии 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]}
}

+Оператор также может быть использован вместо <<.

113
23.05.2017 11:46:46

I can't find anything built in... but using the ExpandoMetaClass I can do this:

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

this adds the collectMap method to all ArrayLists... I'm not sure why adding it to List or Collection didn't work.. I guess that's for another question... but now I can do this...

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

from List to calculated Map with one closure... exactly what I was looking for.

Edit: the reason I couldn't add the method to the interfaces List and Collection was because I did not do this:

List.metaClass.enableGlobally()

after that method call, you can add methods to interfaces.. which in this case means my collectMap method will work on ranges like this:

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

который дает карту: [0: 0, 1: 2, 2: 4]

1
20.08.2008 22:43:50

Кроме того, если вы используете коллекции Google ( http://code.google.com/p/google-collections/ ), вы можете сделать что-то вроде этого:

  map = Maps.uniqueIndex(list, Functions.identity());
5
20.08.2008 22:32:57

хорошо ... я играл с этим немного больше, и я думаю, что это довольно крутой метод ...

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 имеет этот метод ...

здесь я использую его, чтобы изменить ключ / значение на карте

[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
5
21.08.2008 18:21:18

Как насчет этого?

// 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']
0
29.09.2008 17:41:19
That's basically the same as in my question... I just had map[it.k] = it.v instead of .putAt I was looking for a one liner.
danb 13.10.2008 01:43:39

Check out "inject". Real functional programming wonks call it "fold".

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

And, while you're at it, you probably want to define methods as Categories instead of right on the metaClass. That way, you can define it once for all Collections:

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

Example usage:

use(PropertyMapCategory) {
    println columns.mapProperty('name', 'val')
}
28
13.10.2008 18:48:46
I guess it's named inject in Groovy as it might have been inspired by inject:into: in Smalltalk: | 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 2.01.2013 18:41:31

Was the groupBy method not available when this question was asked?

13
12.06.2015 15:00:51
Seems like no - it's since 1.8.1, 2011. The question was asked in 2008. But in any case, groupBy is now the way to go indeed.
mvmn 1.04.2014 21:48:03
As you can see in the documentation for groupBy, it basically groups elements into groups, where each group contains element matching a certain key. Hence, its return type is Map<K, List<V>> It seems that the OP is looking for a method with return type Map<K, V>, so groupBy does not work in this case.
Krzysiek Przygudzki 6.11.2019 13:01:18

If what you need is a simple key-value pair, then the method collectEntries should suffice. For example

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
9.03.2017 08:06:40