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

107

Мне бы для этого нужна какая-то рука:

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
Комментарий Амира теперь является правильным ответом: stackoverflow.com/a/4484958/27561
Роберт Фишер

Ответы:

120

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

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

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, так как он мог быть вдохновлен inject: into: в Smalltalk: | сумма списка | list: = OrderedCollection новое добавление: 1; добавить: 2; добавить: 3; сами. sum: = list inject: 0 into: [: a: b | а + б]. Стенограмма cr; показать: сумма. «отпечатков 6»
OlliP 02
13

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

Амир Раминфар
источник
Похоже, что нет - это с версии 1.8.1, 2011. Вопрос был задан в 2008 году. Но в любом случае groupBy теперь действительно верный путь.
mvmn 01
Как вы можете видеть в документации для groupBy, он в основном группирует элементы в группы, где каждая группа содержит элемент, соответствующий определенному ключу. Следовательно, его тип возвращаемого значения - Map<K, List<V>>похоже, что OP ищет метод с типом возвращаемого значения Map<K, V>, поэтому groupBy в этом случае не работает.
Krzysiek Przygudzki
6

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

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

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
данб
источник
1

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

данб
источник
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']
Майкл Истер
источник
Это в основном то же самое, что и в моем вопросе ... У меня просто была карта [it.k] = it.v вместо .putAt, я искал однострочник.
danb