ярлик для створення карти зі списку в groovy?


106

Я хотів би дещо такого для цього:

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
Роберт Фішер

Відповіді:


119

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

The +Оператор також може бути використаний замість <<.


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: в: у Smalltalk: | сума списку | список: = OrdersCollection new add: 1; додати: 2; додати: 3; себе. sum: = список введення: 0 в: [: a: b | a + b]. Стенограма кр; показати: сума. "друкує 6"
OlliP

13

Чи не був доступний метод groupBy, коли було задано це питання?


Здається, що ні - це починається з 1.8.1, 2011. Питання було задано у 2008 році. Але в будь-якому випадку, групаBy тепер справді йде.
mvmn

Як ви бачите в документації для groupBy, вона в основному об'єднує елементи в групи, де кожна група містить елемент, що відповідає певному ключу. Отже, його тип повернення: Map<K, List<V>>Схоже, що ОП шукає метод з типом повернення 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
}

це додає метод collectionMap до всіх ArrayLists ... я не впевнений, чому додавання його до списку чи колекції не спрацювало. Я думаю, це вже з іншого питання ... але тепер я можу це зробити ...

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

зі списку до розрахункової карти з одним закриттям ... саме те, що я шукав.

Редагувати: причиною того, що я не зміг додати метод до списку інтерфейсів та колекції, було те, що я цього не робив:

List.metaClass.enableGlobally()

після цього виклику методу ви можете додати методи до інтерфейсів .. що в цьому випадку означає, що мій метод collectionMap буде працювати в таких діапазонах:

(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 я шукав один лайнер.
Даб
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.