raccourci pour créer une carte à partir d'une liste dans groovy?

106

J'aimerais une sorte de main pour ceci:

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

étant donné la façon dont les choses GDK sont, je m'attendrais à être capable de faire quelque chose comme:

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

mais je n'ai rien vu dans la documentation ... est-ce que je manque quelque chose? ou suis-je juste trop paresseux?

danb
la source
2
Le commentaire d'Amir est maintenant la bonne réponse: stackoverflow.com/a/4484958/27561
Robert Fischer

Réponses:

119

J'ai récemment découvert la nécessité de faire exactement cela: convertir une liste en carte. Cette question a été postée avant la sortie de la version 1.7.9 de Groovy, donc la méthode collectEntriesn'existait pas encore. Cela fonctionne exactement comme la collectMapméthode qui a été proposée :

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

Si pour une raison quelconque vous êtes coincé avec une ancienne version de Groovy, la injectméthode peut également être utilisée (comme proposé ici ). Il s'agit d'une version légèrement modifiée qui ne prend qu'une seule expression à l'intérieur de la fermeture (juste pour sauver des caractères!):

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

L' +opérateur peut également être utilisé à la place de <<.

épidémien
la source
28

Découvrez "injecter". Les vrais imbéciles de la programmation fonctionnelle l'appellent «pli».

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

Et, pendant que vous y êtes, vous voudrez probablement définir des méthodes en tant que catégories au lieu de directement sur la métaClasse. De cette façon, vous pouvez le définir une fois pour toutes les collections:

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

Exemple d'utilisation:

use(PropertyMapCategory) {
    println columns.mapProperty('name', 'val')
}
Robert Fischer
la source
Je suppose qu'il s'appelle inject dans Groovy car il aurait pu être inspiré par inject: into: in Smalltalk: | liste somme | list: = OrderedCollection nouveau ajouter: 1; ajouter: 2; ajouter: 3; toi même. somme: = liste injecter: 0 dans: [: a: b | a + b]. Transcript cr; montrer: somme. "imprime 6"
OlliP
13

La méthode groupBy n'était- elle pas disponible lorsque cette question a été posée?

Amir Raminfar
la source
On dirait que non - c'est depuis le 1.8.1, 2011. La question a été posée en 2008. Mais dans tous les cas, groupBy est maintenant la voie à suivre.
mvmn
Comme vous pouvez le voir dans la documentation de groupBy, il regroupe essentiellement les éléments en groupes, où chaque groupe contient des éléments correspondant à une certaine clé. Par conséquent, son type de retour est Map<K, List<V>>Il semble que l'OP recherche une méthode avec un type de retour Map<K, V>, donc groupBy ne fonctionne pas dans ce cas.
Krzysiek Przygudzki
6

Si vous avez besoin d'une simple paire clé-valeur, la méthode collectEntriesdevrait suffire. Par exemple

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

Mais si vous voulez une structure similaire à une Multimap, dans laquelle il y a plusieurs valeurs par clé, vous voudrez alors utiliser la groupByméthode

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

ok ... j'ai joué avec ça un peu plus et je pense que c'est une méthode plutôt cool ...

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

maintenant, toutes les sous-classes de Map ou Collection ont cette méthode ...

ici je l'utilise pour inverser la clé / valeur dans une carte

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

et ici je l'utilise pour créer une carte à partir d'une liste

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

maintenant, je saisis simplement ceci dans une classe qui est appelée lorsque mon application démarre et cette méthode est disponible dans tout mon code.

ÉDITER:

pour ajouter la méthode à tous les tableaux ...

Object[].metaClass.collectMap = collectMap
danb
la source
1

Je ne trouve rien de intégré ... mais en utilisant ExpandoMetaClass, je peux le faire:

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

cela ajoute la méthode collectMap à tous les ArrayLists ... Je ne sais pas pourquoi l'ajout à List ou Collection n'a pas fonctionné ... Je suppose que c'est pour une autre question ... mais maintenant je peux le faire ...

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

de la liste à la carte calculée avec une fermeture ... exactement ce que je cherchais.

Edit: la raison pour laquelle je n'ai pas pu ajouter la méthode à la liste des interfaces et à la collection était parce que je ne l'ai pas fait:

List.metaClass.enableGlobally()

après cet appel de méthode, vous pouvez ajouter des méthodes aux interfaces .. ce qui dans ce cas signifie que ma méthode collectMap fonctionnera sur des plages comme celle-ci:

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

ce qui donne la carte: [0: 0, 1: 2, 2: 4]

danb
la source
0

Et quelque chose comme ça?

// 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
la source
C'est fondamentalement la même chose que dans ma question ... Je viens d'avoir map [it.k] = it.v au lieu de .putAt je cherchais une ligne unique.
danb