atajo para crear un mapa a partir de una lista en groovy?

106

Me gustaría tener una mano amiga para esto:

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

dada la forma en que están las cosas de GDK, esperaría poder hacer algo como:

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

pero no he visto nada en los documentos ... ¿me falta algo? ¿O soy demasiado vago?

danb
fuente
2
El comentario de Amir es ahora la respuesta correcta: stackoverflow.com/a/4484958/27561
Robert Fischer

Respuestas:

119

Recientemente me encontré con la necesidad de hacer exactamente eso: convertir una lista en un mapa. Esta pregunta se publicó antes de que saliera la versión 1.7.9 de Groovy, por lo que el método collectEntriesaún no existía. Funciona exactamente como el collectMapmétodo propuesto :

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

Si por alguna razón está atascado con una versión anterior de Groovy, el injectmétodo también se puede utilizar (como se propone aquí ). Esta es una versión ligeramente modificada que solo toma una expresión dentro del cierre (¡solo para guardar el carácter!):

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

El +operador también se puede utilizar en lugar de <<.

epidemia
fuente
28

Echa un vistazo a "inyectar". Los verdaderos expertos en programación funcional lo llaman "pliegue".

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

Y, mientras lo hace, probablemente desee definir métodos como Categorías en lugar de directamente en la metaclase. De esa manera, puede definirlo una vez para todas las colecciones:

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

Uso de ejemplo:

use(PropertyMapCategory) {
    println columns.mapProperty('name', 'val')
}
Robert Fischer
fuente
Supongo que se llama inject en Groovy, ya que podría haberse inspirado en inject: into: en Smalltalk: | lista de suma | list: = OrderedCollection new add: 1; agregar: 2; añadir: 3; usted mismo. suma: = lista inyectar: ​​0 en: [: a: b | a + b]. Transcripción cr; mostrar: suma. "imprime 6"
OlliP
13

¿ El método groupBy no estaba disponible cuando se hizo esta pregunta?

Amir Raminfar
fuente
Parece que no, es desde 1.8.1, 2011. La pregunta se hizo en 2008. Pero en cualquier caso, groupBy es ahora el camino a seguir.
mvmn
Como puede ver en la documentación de groupBy, básicamente agrupa elementos en grupos, donde cada grupo contiene elementos que coinciden con una determinada clave. Por lo tanto, su tipo de retorno es Map<K, List<V>>Parece que el OP está buscando un método con tipo de retorno Map<K, V>, por lo que groupBy no funciona en este caso.
Krzysiek Przygudzki
6

Si lo que necesita es un par clave-valor simple, entonces el método collectEntriesdebería ser suficiente. Por ejemplo

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

Pero si desea una estructura similar a un Multimap, en el que hay varios valores por clave, entonces querrá usar el groupBymétodo

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

ok ... he jugado con esto un poco más y creo que este es un método bastante bueno ...

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

ahora cualquier subclase de Mapa o Colección tiene este método ...

aquí lo uso para revertir la clave / valor en un mapa

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

y aquí lo uso para crear un mapa a partir de una lista

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

ahora simplemente pongo esto en una clase que se llama cuando mi aplicación se está iniciando y este método está disponible en todo mi código.

EDITAR:

para agregar el método a todas las matrices ...

Object[].metaClass.collectMap = collectMap
danb
fuente
1

No puedo encontrar nada integrado ... pero usando ExpandoMetaClass puedo hacer esto:

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

esto agrega el método collectMap a todas las ArrayLists ... No estoy seguro de por qué agregarlo a List o Collection no funcionó ... Supongo que eso es para otra pregunta ... pero ahora puedo hacer esto ...

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

de la lista al mapa calculado con un cierre ... exactamente lo que estaba buscando.

Editar: la razón por la que no pude agregar el método a la Lista y Colección de interfaces fue porque no hice esto:

List.metaClass.enableGlobally()

después de esa llamada al método, puede agregar métodos a las interfaces ... lo que en este caso significa que mi método collectMap funcionará en rangos como este:

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

que produce el mapa: [0: 0, 1: 2, 2: 4]

danb
fuente
0

¿Y algo como esto?

// 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']
Miguel Pascua
fuente
Eso es básicamente lo mismo que en mi pregunta ... Acabo de tener map [it.k] = it.v en lugar de .putAt Estaba buscando una línea.
danb