ярлык для создания карты из списка в Groovy? - PullRequest
99 голосов
/ 20 августа 2008

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

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

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

Ответы [ 8 ]

109 голосов
/ 13 апреля 2011

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

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

28 голосов
/ 13 октября 2008

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

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')
}
13 голосов
/ 19 декабря 2010

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

5 голосов
/ 09 марта 2017

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

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

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
5 голосов
/ 21 августа 2008

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

  map = Maps.uniqueIndex(list, Functions.identity());
1 голос
/ 21 августа 2008

Я не могу найти ничего встроенного ... но с помощью 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 и Collection, была в том, что я не сделал этого:

List.metaClass.enableGlobally()

после вызова этого метода вы можете добавить методы к интерфейсам ... что в данном случае означает, что мой метод collectMap будет работать в таких диапазонах:

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

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

0 голосов
/ 29 сентября 2008

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

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