Ярлык для создания карты из списка в 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] }
}

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

Ответ 1

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

Вместо << можно использовать оператор +.

Ответ 2

Проверьте "впрыск". Реальные функциональные программируемые волки называют это "сгибом".

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')
}

Ответ 3

Был ли метод groupBy недоступен, когда задавался этот вопрос?

Ответ 4

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

  map = Maps.uniqueIndex(list, Functions.identity());

Ответ 5

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

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]

теперь я просто вставляю это в класс, который вызывается при запуске моего приложения, и этот метод доступен во всем моем коде.

EDIT:

чтобы добавить метод ко всем массивам...

Object[].metaClass.collectMap = collectMap

Ответ 6

Я не могу найти ничего встроенного... но с помощью ExpandoMetaClass я могу это сделать:

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

это добавляет метод collectMap ко всем массивам ArrayLists... Я не уверен, почему добавление его в 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]

Ответ 7

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

// 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']

Ответ 8

Если вам нужна простая пара ключей и значений, то метод 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]]