shortcut for creating a Map from a List in groovy?
Asked Answered
S

8

126

I'd like some sorthand for this:

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

given the way the GDK stuff is, I'd expect to be able to do something like:

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

but I haven't seen anything in the docs... am I missing something? or am I just way too lazy?

Subtorrid answered 20/8, 2008 at 18:37 Comment(1)
Amir's comment is now the correct answer: https://mcmap.net/q/179408/-shortcut-for-creating-a-map-from-a-list-in-groovyAdjoin
T
148

I've recently came across the need to do exactly that: converting a list into a map. This question was posted before Groovy version 1.7.9 came out, so the method collectEntries didn't exist yet. It works exactly as the collectMap method that was proposed:

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

If for some reason you are stuck with an older Groovy version, the inject method can also be used (as proposed here). This is a slightly modified version that takes only one expression inside the closure (just for the sake of character saving!):

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

The + operator can also be used instead of the <<.

Toothwort answered 13/4, 2011 at 6:47 Comment(0)
A
32

Check out "inject". Real functional programming wonks call it "fold".

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

And, while you're at it, you probably want to define methods as Categories instead of right on the metaClass. That way, you can define it once for all Collections:

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

Example usage:

use(PropertyMapCategory) {
    println columns.mapProperty('name', 'val')
}
Adjoin answered 13/10, 2008 at 18:48 Comment(1)
I guess it's named inject in Groovy as it might have been inspired by inject:into: in Smalltalk: | list sum | list := OrderedCollection new add: 1; add: 2; add: 3; yourself. sum := list inject: 0 into: [ :a :b | a + b ]. Transcript cr; show: sum. "prints 6"Pumping
F
13

Was the groupBy method not available when this question was asked?

Favien answered 19/12, 2010 at 20:34 Comment(2)
Seems like no - it's since 1.8.1, 2011. The question was asked in 2008. But in any case, groupBy is now the way to go indeed.Masterly
As you can see in the documentation for groupBy, it basically groups elements into groups, where each group contains element matching a certain key. Hence, its return type is Map<K, List<V>> It seems that the OP is looking for a method with return type Map<K, V>, so groupBy does not work in this case.Hyetal
E
11

If what you need is a simple key-value pair, then the method collectEntries should suffice. For example

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

But if you want a structure similar to a Multimap, in which there are multiple values per key, then you'd want to use the groupBy method

def names = ['Foo', 'Bar', 'Fooey']
def firstAlphabetVsNames = names.groupBy { it.charAt(0) } // [F:[Foo, Fooey], B:[Bar]]
Eastwards answered 9/3, 2017 at 8:6 Comment(0)
I
6

Also, if you're use google collections (http://code.google.com/p/google-collections/), you can do something like this:

  map = Maps.uniqueIndex(list, Functions.identity());
Imogeneimojean answered 20/8, 2008 at 22:32 Comment(0)
S
5

ok... I've played with this a little more and I think this is a pretty cool method...

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

now any subclass of Map or Collection have this method...

here I use it to reverse the key/value in a Map

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

and here I use it to create a map from a list

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

now I just pop this into a class that gets called as my app is starting and this method is available throughout my code.

EDIT:

to add the method to all arrays...

Object[].metaClass.collectMap = collectMap
Subtorrid answered 20/8, 2008 at 23:28 Comment(0)
S
1

I can't find anything built in... but using the ExpandoMetaClass I can do this:

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

this adds the collectMap method to all ArrayLists... I'm not sure why adding it to List or Collection didn't work.. I guess that's for another question... but now I can do this...

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

from List to calculated Map with one closure... exactly what I was looking for.

Edit: the reason I couldn't add the method to the interfaces List and Collection was because I did not do this:

List.metaClass.enableGlobally()

after that method call, you can add methods to interfaces.. which in this case means my collectMap method will work on ranges like this:

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

which yields the map: [0:0, 1:2, 2:4]

Subtorrid answered 20/8, 2008 at 22:25 Comment(0)
S
0

What about something like this?

// 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']
Shantae answered 29/9, 2008 at 17:41 Comment(1)
That's basically the same as in my question... I just had map[it.k] = it.v instead of .putAt I was looking for a one liner.Subtorrid

© 2022 - 2024 — McMap. All rights reserved.