skrót do tworzenia mapy z listy w groovy?

106

Chciałbym na to trochę odpocząć:

Map rowToMap(row) {
    def rowMap = [:];
    row.columns.each{ rowMap[it.name] = it.val }
    return rowMap;
}

biorąc pod uwagę stan rzeczy z GDK, spodziewałbym się, że będę w stanie zrobić coś takiego:

Map rowToMap(row) {
    row.columns.collectMap{ [it.name,it.val] }
}

ale nie widziałem nic w dokumentach ... czy coś mi brakuje? czy jestem po prostu zbyt leniwy?

danb
źródło
2
Komentarz Amira jest teraz poprawną odpowiedzią: stackoverflow.com/a/4484958/27561
Robert Fischer

Odpowiedzi:

119

Niedawno natknąłem się na potrzebę zrobienia dokładnie tego: przekonwertowania listy na mapę. To pytanie zostało wysłane przed pojawieniem się Groovy w wersji 1.7.9, więc metoda collectEntriesjeszcze nie istniała. Działa dokładnie tak, jak zaproponowanacollectMap metoda :

Map rowToMap(row) {
    row.columns.collectEntries{[it.name, it.val]}
}

Jeśli z jakiegoś powodu utkniesz ze starszą wersją Groovy, injectmożesz również użyć metody (jak zaproponowano tutaj ). To jest nieco zmodyfikowana wersja, która przyjmuje tylko jedno wyrażenie wewnątrz zamknięcia (tylko ze względu na zapisywanie znaków!):

Map rowToMap(row) {
    row.columns.inject([:]) {map, col -> map << [(col.name): col.val]}
}

+Operator może również być stosowany zamiast <<.

epidemia
źródło
28

Sprawdź "wstrzyknąć". Prawdziwi fani programowania funkcjonalnego nazywają to „pasowaniem”.

columns.inject([:]) { memo, entry ->
    memo[entry.name] = entry.val
    return memo
}

A skoro już to robisz, prawdopodobnie chcesz zdefiniować metody jako kategorie zamiast bezpośrednio w metaClass. W ten sposób możesz zdefiniować go raz dla wszystkich kolekcji:

class PropertyMapCategory {
    static Map mapProperty(Collection c, String keyParam, String valParam) {
        return c.inject([:]) { memo, entry ->
            memo[entry[keyParam]] = entry[valParam]
            return memo
        }
    }
}

Przykładowe użycie:

use(PropertyMapCategory) {
    println columns.mapProperty('name', 'val')
}
Robert Fischer
źródło
Wydaje mi się, że w Groovy to się nazywa inject, ponieważ mogło być zainspirowane przez inject: into: w Smalltalk: | suma listy | list: = OrderedCollection nowy dodaj: 1; dodać: 2; dodać: 3; siebie. sum: = lista wstrzyknąć: 0 do: [: a: b | a + b]. Transcript cr; pokaż: suma. "wydruki 6"
OlliP
13

Czy metoda groupBy nie była dostępna, gdy zadano to pytanie?

Amir Raminfar
źródło
Wydaje się, że nie - jest od 1.8.1 2011. Pytanie zadano w 2008 roku. W każdym razie groupBy jest teraz właściwą drogą.
mvmn
Jak widać w dokumentacji groupBy, w zasadzie grupuje elementy w grupy, z których każda zawiera element pasujący do określonego klucza. W związku z tym jego typem zwracanym jest Map<K, List<V>>Wydaje się, że OP szuka metody z typem zwracanym Map<K, V>, więc groupBy nie działa w tym przypadku.
Krzysiek Przygudzki
6

Jeśli potrzebujesz prostej pary klucz-wartość, metoda collectEntriespowinna wystarczyć. Na przykład

def names = ['Foo', 'Bar']
def firstAlphabetVsName = names.collectEntries {[it.charAt(0), it]} // [F:Foo, B:Bar]

Ale jeśli chcesz mieć strukturę podobną do Multimap, w której istnieje wiele wartości na klucz, powinieneś użyć groupBymetody

def names = ['Foo', 'Bar', 'Fooey']
def firstAlphabetVsNames = names.groupBy { it.charAt(0) } // [F:[Foo, Fooey], B:[Bar]]
Nerrve
źródło
5

ok ... bawiłem się tym trochę dłużej i myślę, że to całkiem fajna metoda ...

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

teraz każda podklasa Map lub Collection ma tę metodę ...

tutaj używam go do odwrócenia klucza / wartości w Mapie

[1:2, 3:4].collectMap{[it.value, it.key]} == [2:1, 4:3]

i tutaj używam go do tworzenia mapy z listy

[1,2].collectMap{[it,it]} == [1:1, 2:2]

teraz po prostu umieszczam to w klasie, która jest wywoływana podczas uruchamiania mojej aplikacji, a ta metoda jest dostępna w całym moim kodzie.

EDYTOWAĆ:

dodać metodę do wszystkich tablic ...

Object[].metaClass.collectMap = collectMap
danb
źródło
1

Nie mogę znaleźć nic wbudowanego ... ale używając ExpandoMetaClass mogę to zrobić:

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

to dodaje metodę collectMap do wszystkich ArrayLists ... Nie jestem pewien, dlaczego dodanie jej do Listy lub Kolekcji nie zadziałało .. Myślę, że to inne pytanie ... ale teraz mogę to zrobić ...

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

z listy do obliczonej mapy z jednym zamknięciem ... dokładnie to, czego szukałem.

Edycja: powodem, dla którego nie mogłem dodać metody do listy i kolekcji interfejsów, było to, że nie zrobiłem tego:

List.metaClass.enableGlobally()

po wywołaniu metody możesz dodać metody do interfejsów .. co w tym przypadku oznacza, że ​​moja metoda collectMap będzie działać na zakresach takich jak ten:

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

co daje mapę: [0: 0, 1: 2, 2: 4]

danb
źródło
0

A co z czymś takim?

// 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']
Michała Wielkanocy
źródło
To w zasadzie to samo, co w moim pytaniu ... Miałem po prostu map [it.k] = it.v zamiast .putAt Szukałem jednej linijki.
danb