atalho para criar um mapa de uma lista no groovy?

106

Eu gostaria de algum tipo de mão para isso:

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

dada a forma como as coisas do GDK são, espero ser capaz de fazer algo como:

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

mas eu não vi nada nos documentos ... estou faltando alguma coisa? ou eu sou apenas preguiçoso?

danb
fonte
2
O comentário de Amir agora é a resposta correta: stackoverflow.com/a/4484958/27561
Robert Fischer

Respostas:

119

Recentemente, descobri a necessidade de fazer exatamente isso: converter uma lista em um mapa. Esta questão foi postada antes do lançamento da versão 1.7.9 do Groovy, então o método collectEntriesainda não existia. Funciona exatamente como o collectMapmétodo proposto :

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

Se por algum motivo você ficar preso a uma versão mais antiga do Groovy, o injectmétodo também pode ser usado (conforme proposto aqui ). Esta é uma versão ligeiramente modificada que leva apenas uma expressão dentro do encerramento (apenas para salvar caracteres!):

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

O +operador também pode ser usado no lugar do <<.

epidêmico
fonte
28

Confira "injetar". Os verdadeiros especialistas em programação funcional chamam isso de "dobrar".

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

E, enquanto você está nisso, provavelmente deseja definir métodos como categorias em vez de diretamente na metaClasse. Dessa forma, você pode defini-lo uma vez para todas as coleções:

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

Exemplo de uso:

use(PropertyMapCategory) {
    println columns.mapProperty('name', 'val')
}
Robert Fischer
fonte
Acho que se chama injetar no Groovy, pois pode ter sido inspirado por injetar: em: em Smalltalk: | soma da lista | lista: = OrderedCollection novo adicionar: 1; adicionar: 2; adicionar: 3; você mesmo. soma: = lista injetar: 0 em: [: a: b | a + b]. Transcript cr; show: sum. "imprime 6"
OlliP
13

O método groupBy não estava disponível quando esta pergunta foi feita?

Amir Raminfar
fonte
Parece que não - é desde 1.8.1 de 2011. A pergunta foi feita em 2008. Mas, em qualquer caso, groupBy agora é o caminho a percorrer.
mvmn de
Como você pode ver na documentação de groupBy, basicamente agrupa elementos em grupos, onde cada grupo contém um elemento correspondente a uma determinada chave. Portanto, seu tipo de retorno é Map<K, List<V>>. Parece que o OP está procurando um método com tipo de retorno Map<K, V>, portanto groupBy não funciona neste caso.
Krzysiek Przygudzki
6

Se você precisa de um par de valores-chave simples, o método collectEntriesdeve ser suficiente. Por exemplo

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

Mas se você quiser uma estrutura semelhante a um multimapa, na qual existem vários valores por chave, você deve usar o groupBymétodo

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

ok ... brinquei um pouco mais com isso e acho que é um método bem legal ...

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

agora qualquer subclasse de Mapa ou Coleção tem este método ...

aqui eu uso para reverter a chave / valor em um mapa

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

e aqui eu uso para criar um mapa a partir de uma lista

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

agora eu apenas coloco isso em uma classe que é chamada quando meu aplicativo está iniciando e esse método está disponível em todo o meu código.

EDITAR:

para adicionar o método a todos os arrays ...

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

Não consigo encontrar nada integrado ... mas usando ExpandoMetaClass posso fazer isso:

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

isso adiciona o método collectMap a todos os ArrayLists ... Não tenho certeza por que adicioná-lo a List ou Collection não funcionou. Acho que é outra questão ... mas agora posso fazer isso ...

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

da lista ao mapa calculado com um fechamento ... exatamente o que eu estava procurando.

Editar: o motivo pelo qual não consegui adicionar o método às interfaces Lista e Coleção foi porque não fiz isso:

List.metaClass.enableGlobally()

após essa chamada de método, você pode adicionar métodos a interfaces .. o que, neste caso, significa que meu método collectMap funcionará em intervalos como este:

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

que produz o mapa: [0: 0, 1: 2, 2: 4]

danb
fonte
0

Que tal algo assim?

// 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']
Michael Easter
fonte
É basicamente o mesmo que na minha pergunta ... Eu tinha map [it.k] = it.v em vez de .putAt. Eu estava procurando por um liner.
danb