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

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

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

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

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

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

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

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

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

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

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

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

Я не могу найти ничего встроенного... но с помощью 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]

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

// 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']
Другие вопросы по тегам