Optimize your code with Apache Groovy’s spread operator
Enhance your understanding of Groovy Collections with the spread operator. This guide delves into how it can be used for organizing, combining, and altering lists and maps, ultimately improving the readability and effectiveness of your code.
We’ve covered how the Closure facilitates operating on list or map elements, taking a particularly careful look at sorting. Now we’re going to take a look at the spread operator, which provides a different way to apply transformations to the elements of a list or map. (If you haven’t installed Groovy yet, please read the intro to this series.)
Groovy has two related operators that provide a nice concise way to do things to all the members of a Collection – the spread-dot operator – and to “de-collect” the members of a collection into individual elements – the spread operator. Let’s see how we can use these to make our Groovy code more compact, expressive, readable and maintainable.
First, the spread operator helps with merging lists and maps.
Consider this:
1 def l = [1, 5, 6, 7]
2 def s = [2, 3, 4]
3 l[2] = s
4 println "l $l"
5 l = [1, 5, 6, 7]
6 l = [*l.take(1), *s, *l.takeRight(l.size() - 1)]
7 println "l $l"
Line one defines the list l with an obvious gap between the first and second element values.
Line two defines the sublist s that could fit in the gap in l.
Line three uses the putAt() method of the List interface to insert the the sub-list s into the list l – the line l[2] = s is equivalent to l.putAt(2, s).
Line four prints the result.
Line five resets l to its original value.
Line six uses the take() method to get a sublist of the first element of l and the takeRight() method to get a sublist of the remaining elements of l. By putting the *, or spread operator, in front of each of the sublists in line 6, we “de-list” them into their elements.
Line seven prints the result.
Let’s see what that looks like:
$ groovy Groovy17a.groovy
l [1, 5, [2, 3, 4], 7]
l [1, 2, 3, 4, 5, 6, 7]
$
Note that the first attempt leaves the list l with the sublist s embedded in it, whereas the second attempt gives us a new flat list.
We can apply the flatten() method to l in the first case to promote the sublist elements into the list.
We can perform the same process on maps as lists, with one small syntactic change – the * operator is used together with the colon – if sm is the submap, then *:sm extracts the members of sm into the map.
The *. operator is a way of applying a method to each element of a list or map. Here’s an example:
1 def l = [1, 2, 3, 4, 5]
2 println "l $l"
3 println "l*.plus(2) ${l*.plus(2)}"
4 println "l*.multiply(2) ${l*.multiply(2)}"
5 println "l*.power(2) ${l*.power(2)}"
Line one defines our test list containing the integers 1 through 5.
Line two prints the list.
Line three prints a new list whose elements are each two more than the original elements of l.
Line four prints a new list whose elements are each twice the original elements of l.
Line five prints a new list whose elements are each the square of the original elements of l.
Run it:
$ groovy Groovy17b.groovy
l [1, 2, 3, 4, 5]
l*.plus(2) [3, 4, 5, 6, 7]
l*.multiply(2) [2, 4, 6, 8, 10]
l*.power(2) [1, 4, 9, 16, 25]
$
You can produce the same result using the collect() method of List:
l*.plus(2)
That produces the same result as:
l.collect { e -> e + 2 }
This equivalency suggests that we can dig out field values from lists of class instances or map values by key:
1 def l = [
2 [temp: 96.3, gender: 'male', pulse: 70],
3 [temp: 96.7, gender: 'male', pulse: 71],
4 [temp: 96.9, gender: 'male', pulse: 74],
5 [temp: 97.0, gender: 'male', pulse: 80],
6 [temp: 97.1, gender: 'male', pulse: 73],
7 [temp: 97.1, gender: 'male', pulse: 75],
8 [temp: 97.1, gender: 'male', pulse: 82],
9 [temp: 97.2, gender: 'male', pulse: 64],
10 [temp: 97.3, gender: 'male', pulse: 69],
11 [temp: 99.0, gender: 'female', pulse: 81],
12 [temp: 99.1, gender: 'female', pulse: 80],
13 [temp: 99.1, gender: 'female', pulse: 74],
14 [temp: 99.2, gender: 'female', pulse: 77],
15 [temp: 99.2, gender: 'female', pulse: 66],
16 [temp: 99.3, gender: 'female', pulse: 68],
17 [temp: 99.4, gender: 'female', pulse: 77],
18 [temp: 99.9, gender: 'female', pulse: 79],
19 [temp: 100.0, gender: 'female', pulse: 78],
20 [temp: 100.8, gender: 'female', pulse: 77]]
21 println "l*.temp ${l*.temp}"
Run it:
$ groovy Groovy17c.groovy
l*.temp [96.3, 96.7, 96.9, 97.0, 97.1, 97.1, 97.1, 97.2, 97.3, 99.0, 99.1, 99.1, 99.2, 99.2, 99.3, 99.4, 99.9, 100.0, 100.8]
$
Conclusion
The spread operator, *, is a way to extract the elements of a collection into a surrounding collection. The spread-dot operator, *., is a way to apply methods to each of the elements of the collection, or to extract a specific field or key-value pair from a list of class instances or maps.
Both operations can be realized using other facilities in Groovy, but it may be a good thing to adopt the “list-as-a-whole” or “map-as-a-whole” point of view that spread and particularly spread-dot provides.
Calling all LISP enthusiasts: Are there any hidden gems or unique use cases for the spread and spread-dot operators that I might be overlooking?
