Maps

Check if Maps are Equal

With Groovy 1.8 the equals() method is added to Map. This means we can check if maps are equals. They are equals if both maps have the same size, and keys and values are the same.

def map1 = [user: 'mrhaki', likes: 'Groovy', age: 37]
def map2 = [age: 37.0, likes: 'Groovy', user: 'mrhaki']
def map3 = [user: 'Hubert Klein Ikkink', likes: 'Groovy']

assert map1.equals(map2)
assert map1 == map2
assert !map1.equals(map3)
assert map2 != map3

Original post written on April 27, 2011

Sorting a Map

Maps don't have an order for the elements, but we may want to sort the entries in the map. Since Groovy 1.7.2 we can use the sort() method which uses the natural ordering of the keys to sort the entries. Or we can pass a Comparator to the sort() method to define our own sorting algorithm for the keys.

def m = [sort: 'asc', name: 'test', paginate: true, max: 100]

def expectedKeys = ['max', 'name', 'paginate', 'sort'] 
assert expectedKeys == m.sort()*.key  // Since 1.7.2
assert expectedKeys == m.sort( { k1, k2 -> k1 <=> k2 } as Comparator )*.key // Since 1.7.2

// Sorting before Groovy 1.7.2
assert expectedKeys == new TreeMap(m)*.key
assert expectedKeys == m.sort { e1, e2 -> e1.key <=> e2.key }*.key  // Sort by closure.

Original post written on April 20, 2010

Turn a List into a Map

With Groovy we can use the values of an Object array and transform them to a map with the toSpreadMap() method. The array must have an even number of elements, because the odd elements are the keys for the new map and the even numbers are the values for the keys. The SpreadMap object, which now contains the keys and values, is an immutable map, so we cannot change the contents once we have created the map.

def list = ['key', 'value', 'name', 'mrhaki'] as Object[]
def map = list.toSpreadMap()

assert 2 == map.size()
assert 'value' == map.key
assert 'mrhaki' == map['name']

Original post written on January 4, 2010

Complex Keys in Maps

In Groovy we can use non-string keys for maps. We only have to place parenthesis around the key to make it work. This way we can use variables and types like Date and Boolean as keys for our map. When we use parenthesis around the key when using the . notation the key is converted to a String, otherwise the key is not converted and keeps it type.

def key = 100  // Variable to be used a key.

def m = [
    (new Date(109, 11, 1)): 'date key', 
    (-42): 'negative number key', 
    (false): 'boolean key',
    (key): 'variable key'
]
m.(true) = 'boolean key'  // Key is converted to String.
m.(2 + 2) = 'number key'
m[(key + 1)] = 'number key'  // Key keeps to be Integer.

assert 'date key' == m[new Date(109, 11, 1)]
assert 'negative number key' == m.get(-42)
assert 'boolean key' == m[(false)]
assert 'variable key' == m[100]
assert 'variable key' == m.getAt(key)
assert 'boolean key' == m['true']  // Key is String so we can use it to get the value.
assert 'number key' == m.'4'
assert 'number key' == m.get(101)

Original post written on November 7, 2009

Use inject Method on a Map

The inject() method is since Groovy 1.8.1 also available for Map objects. The closure arguments accepts two or three arguments. With the three-argument variant we get the key and value separately as arguments. Otherwise we get a map entry as closure argument.

// 3-argument closure with key, value.
def m = [user: 'mrhaki', likes: 'Groovy']
def sentence = m.inject('Message: ') { s, k, v ->
    s += "${k == 'likes' ? 'loves' : k} $v "
}

assert sentence.trim() == 'Message: user mrhaki loves Groovy'

// 2-argument closure with entry. 
def map = [sort: 'name', order: 'desc']
def equalSizeKeyValue = map.inject([]) { list, entry ->
    list << (entry.key.size() == entry.value.size())
}

assert equalSizeKeyValue == [true, false]

Original post written on September 27, 2011

Intersect Maps

Since Groovy 1.7.4 we can intersect two maps and get a resulting map with only the entries found in both maps.

def m1 = [a: 'Groovy', b: 'rocks', c: '!']
def m2 = [a: 'Groovy', b: 'rocks', c: '?', d: 'Yes!']

assert [a: 'Groovy', b: 'rocks'] == m1.intersect(m2)

assert [1: 1.0, 2: 2] == [1: 1.0, 2: 2].intersect([1: 1, 2: 2.0])

Original post written on August 9, 2010

Subtracting Map Entries

Groovy 1.7.4 adds the minus() method to the Map class. The result is a new map with the entries of the map minus the same entries from the second map.

def m1 = [user: 'mrhaki', age: 37]
def m2 = [user: 'mrhaki', name: 'Hubert']
def m3 = [user: 'Hubert', age: 37]

assert [age: 37] == m1 - m2
assert [user: 'mrhaki'] == m1 - m3

Original post written on August 9, 2010

Process Map Entries in Reverse

Since Groovy 1.7.2 we can loop through a Map in reverse with the reverseEach(). The order in which the content is processed is not guaranteed with a Map. If we use a TreeMap the natural ordering of the keys of the map is used.

def reversed = [:]
[a: 1, c: 3, b: 2].reverseEach { key, value ->
    reversed[key] = value ** 2
}

assert [b: 4, c: 9, a: 1] == reversed

// TreeMap uses natural ordering of keys, so 
// reverseEach starts with key 'c'.
def tree = [a: 10, c: 30, b: 20] as TreeMap
def reversedMap = [:]
tree.reverseEach {
    reversedMap[it.key] = it.value * 2
}
assert [c: 60, b: 40, a: 20] == reversedMap

Original post written on August 24, 2010

Getting a Submap from a Map

To get only a subset of a map we can use the subMap() method. We provide a list of keys as parameter to define which elements from the map we want returned.

def map = [name: 'mrhaki', country: 'The Netherlands', blog: true, languages: ['Groovy', 'Java']]

def keys = ['name', 'blog']
assert [name: 'mrhaki', blog: true] == map.subMap(keys)

def booleanKeys = map.findAll { it.value instanceof Boolean }.collect { it.key } 
assert [blog: true] == map.subMap(booleanKeys)

def words = ['a': 'Apple', 'j': 'Java', 'g': 'Groovy', 'c': 'Cool']
def range = 'c'..'h'  // Range is also a list and can be used here.
def rangeWords = words.subMap(range).findAll{ it.value }
// words.subMap(range) returns [c:Cool, d:null, e:null, f:null, g:Groovy, h:null]
// so we use the findAll method to filter out all null values.
assert ['c': 'Cool', 'g': 'Groovy'] == rangeWords

Original post written on October 29, 2009

Grouping Map Elements

In a previous Groovy Goodness post we learned how to use the groupBy method on collections. The Map class has an extra method: groupEntriesBy. We must provide a closure for this method to define how we want the elements of the map to be grouped. The result is a new Map with keys and a list of Map$Entry objects for each key. This is different from the result of the groupBy method. Because then we get a Map with keys and a Map for each key.

// A simple map.
def m = [q1: 'Groovy', sort: 'desc', q2: 'Grails']

// Closure we use to define the grouping.
// We want all keys starting with 'q' grouped together
// with the key 'params', all other keys are not grouped.
def groupIt = { key, value ->
    if (key.startsWith('q')) { 
        'params'
    } else {
        key
    }
}

// Use groupEntriesBy.
def groupEntries = m.groupEntriesBy(groupIt)
assert 2 == groupEntries.size()
assert groupEntries.params & groupEntries.sort
assert 'desc' == groupEntries.sort[0].value  // Key for a list of Map$Entry objects.
assert 2 == groupEntries.params.size()
assert 'Groovy' == groupEntries.params[0].value
assert 'q1' == groupEntries.params[0].key
assert 'Grails' == groupEntries.params.find { it.key == 'q2' }.value
assert groupEntries.params instanceof ArrayList
assert groupEntries.params[0] instanceof Map$Entry

// Use groupBy.
def group = m.groupBy(groupIt)
assert 2 == group.size()
assert group.params & group.sort
assert 'desc' == group.sort.sort  // Key for Map with key/value pairs.
assert 2 == group.params.size()
assert 'Groovy' == group.params.q1
assert 'q1' == group.params.keySet().toArray()[0]
assert 'Grails' == group.params.q2
assert group.params instanceof Map
assert group.params.q1 instanceof String

Original post written on October 14, 2009

Get Value from Map or a Default Value

The get() method in the Groovy enhanced Map interface accepts two parameters. The first parameter is the name of the key we want to get a value for. And the second parameter is the default value if there is no value for the key.

// Simple map.
def m = [name: 'mrhaki', language: 'Groovy']

assert 'mrhaki' == m.getAt('name')
assert 'mrhaki' == m['name']
assert 'Groovy' == m.language
assert 'mrhaki' == m."name"
assert 'mrhaki' == m.get('name')  // We can omit the default value if we know the key exists.
assert 'Groovy' == m.get('language', 'Java')
assert null == m.get('expression')  // Non-existing key in map.
assert 'rocks' == m.get('expression', 'rocks')  // Use default value, this also creates the key/value \
pair in the map.
assert 'rocks' ==  m.get('expression')
assert [name: 'mrhaki', language: 'Groovy', expression: 'rocks'] == m

Run this script in Groovy web console.

Original post written on November 3, 2009

Map with Default Values

In Groovy we can create a map and use the withDefault() method with a closure to define default values for keys that are not yet in the map. The value for the key is then added to the map, so next time we can get the value from the map.

def m = [start: 'one'].withDefault { key -> 
    key.isNumber() ? 42 : 'Groovy rocks!'
}

assert 'one' == m.start
assert 42 == m['1']
assert 'Groovy rocks!' == m['I say']
assert 3 == m.size()

// We can still assign our own values to keys of course:
m['mrhaki'] = 'Hubert Klein Ikkink'
assert 'Hubert Klein Ikkink' == m.mrhaki
assert 4 == m.size()

Original post written on July 14, 2010

Determine Min and Max Entries in a Map

Since Groovy 1.7.6 we can use the min() and max() methods on a Map. We use a closure to define the condition for a minimum or maximum value. If we use two parameters in the closure we must do a classic comparison. We return a negative value if the first parameters is less than the second, zero if they are both equal, or a positive value if the first parameter is greater than the second parameter. If we use a single parameter we can return a value that is used as Comparable for determining the maximum or minimum entry in the Map.

def money = [cents: 5, dime: 2, quarter: 3]

// Determine max entry.
assert money.max { it.value }.value == 5
assert money.max { it.key }.key == 'quarter'  // Use String comparison for key.
assert money.max { a, b ->
    a.key.size() <=> b.key.size() 
}.key == 'quarter'  // Use Comparator and compare key size.

// Determine min entry.
assert money.min { it.value }.value == 2
assert money.min { it.key }.key == 'cents'  // Use String comparison for key.
assert money.min { a, b ->
    a.key.size() <=> b.key.size() 
}.key == 'dime'  // Use Comparator and compare key size.

Original post written on December 16, 2010

Represent Map As String

Groovy adds to Map objects the toMapString method. With this method we can have a String representation of our Map. We can specify an argument for the maximum width of the generated String. Groovy will make sure at least the key/value pairs are added as a pair, before adding three dots (...) if the maximum size is exceeded.

def course = [
    name: 'Groovy 101',
    teacher: 'mrhaki',
    location: 'The Netherlands']
    
assert course.toMapString(15) == '[name:Groovy 101, ...]'
assert course.toMapString(25) == '[name:Groovy 101, teacher:mrhaki, ...]'

As mentioned in a previous post we can use the toListString method to represent a List as a String:

def names = ['mrhaki', 'hubert']

assert names.toListString(5) == '[mrhaki, ...]'

Written with Groovy 2.4.7.

Original post written on June 21, 2016

Turn A Map Or List As String To Map Or List

In a previous post we learned how to use the toListString or toMapString methods. With these methods we create a String representation of a List or Map object. With a bit of Groovy code we can take such a String object and turn it into a List or Map again.

In the following code snippet we turn the String value [abc, 123, Groovy rocks!] to a List with three items:

// Original List with three items.
def original = ['abc', 123, 'Groovy rocks!']

// Create a String representation:
// [abc, 123, Groovy rocks!]
def listAsString = original.toListString()

// Take the String value between
// the [ and ] brackets, then
// split on , to create a List
// with values.
def list = listAsString[1..-2].split(', ')

assert list.size() == 3
assert list[0] == 'abc'
assert list[1] == '123' // String value
assert list[2] == 'Groovy rocks!'

We can do something similar for a String value representing a map structure:

// Original Map structure.
def original = [name: 'mrhaki', age: 42]

// Turn map into String representation:
// [name:mrhaki, age:42]
def mapAsString = original.toMapString()

def map = 
    // Take the String value between
    // the [ and ] brackets.
    mapAsString[1..-2]
        // Split on , to get a List.
        .split(', ')
        // Each list item is transformed
        // to a Map entry with key/value.
        .collectEntries { entry -> 
            def pair = entry.split(':')
            [(pair.first()): pair.last()]
        }
        

assert map.size() == 2
assert map.name == 'mrhaki'
assert map.age == '42'

Written with Groovy 2.4.7.

Original post written on June 22, 2016