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?


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 <<.


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

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?


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


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

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]


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

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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.