scorciatoia per creare una mappa da un elenco in groovy?


106

Vorrei un po 'di mano per questo:

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

dato il modo in cui sono le cose GDK, mi aspetto di essere in grado di fare qualcosa come:

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

ma non ho visto niente nei documenti ... mi manca qualcosa? o sono semplicemente troppo pigro?


2
Il commento di Amir ora è la risposta corretta: stackoverflow.com/a/4484958/27561
Robert Fischer

Risposte:


119

Di recente ho riscontrato la necessità di fare esattamente questo: convertire un elenco in una mappa. Questa domanda è stata pubblicata prima che uscisse la versione 1.7.9 di Groovy, quindi il metodo collectEntriesnon esisteva ancora. Funziona esattamente come il collectMapmetodo che è stato proposto :

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

Se per qualche motivo sei bloccato con una versione precedente di Groovy, injectpuoi utilizzare anche il metodo (come proposto qui ). Questa è una versione leggermente modificata che richiede una sola espressione all'interno della chiusura (solo per il bene del salvataggio del carattere!):

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

L' +operatore può essere utilizzato anche al posto di <<.


28

Controlla "inject". I veri fanatici della programmazione funzionale lo chiamano "piega".

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

E, già che ci sei, probabilmente vorrai definire i metodi come Categorie invece che direttamente sulla metaClass. In questo modo, puoi definirlo una volta per tutte le raccolte:

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

Utilizzo di esempio:

use(PropertyMapCategory) {
    println columns.mapProperty('name', 'val')
}

Immagino che si chiami inject in Groovy poiché potrebbe essere stato ispirato da inject: into: in Smalltalk: | lista somma | list: = OrderedCollection new add: 1; aggiungi: 2; aggiungi: 3; te stesso. sum: = list inject: 0 in: [: a: b | a + b]. Transcript cr; mostra: somma. "stampe 6"
OlliP

13

È stato il groupBy metodo non è disponibile quando questa domanda è stato chiesto?


Sembra no - è dal 1.8.1, 2011. La domanda è stata posta nel 2008. Ma in ogni caso, groupBy è ora davvero la strada da percorrere.
mvmn

Come puoi vedere nella documentazione di groupBy, fondamentalmente raggruppa gli elementi in gruppi, dove ogni gruppo contiene elementi che corrispondono a una determinata chiave. Quindi, il suo tipo di ritorno è Map<K, List<V>>Sembra che l'OP stia cercando un metodo con tipo di ritorno Map<K, V>, quindi groupBy non funziona in questo caso.
Krzysiek Przygudzki

6

Se ciò di cui hai bisogno è una semplice coppia chiave-valore, il metodo collectEntriesdovrebbe essere sufficiente. Per esempio

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

Ma se vuoi una struttura simile a una Multimap, in cui ci sono più valori per chiave, allora dovresti usare il groupBymetodo

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


5

ok ... ci ho giocato ancora un po 'e penso che sia un metodo piuttosto interessante ...

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

ora qualsiasi sottoclasse di Map o Collection ha questo metodo ...

qui lo uso per invertire la chiave / valore in una mappa

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

e qui lo uso per creare una mappa da un elenco

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

ora lo inserisco in una classe che viene chiamata all'avvio della mia app e questo metodo è disponibile in tutto il codice.

MODIFICARE:

per aggiungere il metodo a tutti gli array ...

Object[].metaClass.collectMap = collectMap

1

Non riesco a trovare nulla di integrato ... ma usando ExpandoMetaClass posso farlo:

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

questo aggiunge il metodo collectMap a tutti gli ArrayList ... Non sono sicuro del motivo per cui aggiungerlo a List o Collection non ha funzionato .. Immagino sia per un'altra domanda ... ma ora posso farlo ...

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

dalla Lista alla Mappa calcolata con una chiusura ... esattamente quello che stavo cercando.

Modifica: il motivo per cui non ho potuto aggiungere il metodo all'elenco e alla raccolta delle interfacce era perché non l'ho fatto:

List.metaClass.enableGlobally()

dopo quella chiamata al metodo, puoi aggiungere metodi alle interfacce .. il che in questo caso significa che il mio metodo collectMap funzionerà su intervalli come questo:

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

che restituisce la mappa: [0: 0, 1: 2, 2: 4]


0

Che ne dici di qualcosa di simile?

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

Questo è fondamentalmente lo stesso della mia domanda ... Avevo solo map [it.k] = it.v invece di .putAt stavo cercando una linea.
danb
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.