Wednesday, April 15, 2009

Groovy's groupBy and inject methods

groupBy



Aman Aggarwal blogged about a powerful method from the java.util.Collection class. The groupBy method converts a collection into a Map with the keys being returned by the closure passed to the method.

Example:

def map = invoices.groupBy { it.invoiceDate.format("MM/yyyy") }
map.each { k, v ->
println "There are ${v.size()} invoices in ${k}"
}


Incidentally, the example above uses another cool function from java.util.Date: format. The method takes a date pattern (from the specification of java.text.SimpleDateFormat) and returns a String.

inject



While writing about groupBy, I thought I might as well talk about the inject() method too. It is a method that is best explained with an example. If I want to add all the numbers from 1 until 10, i.e. 1 + 2 + 3 + 4 + ... + 10, I can do it in one line:

def sum = (1..10).inject(0) { a, b -> a += b }

0 is passed as the initial value. In each iteration, the value from the last iteration is passed as a while the current value is passed as b. If we add a println statement, e.g.

def sum = (1..10).inject(0) { a, b ->
println "${a} + ${b} = ${a + b}"
a += b
}

The output will be:

0 + 1 = 1
1 + 2 = 3
3 + 3 = 6
6 + 4 = 10
10 + 5 = 15
15 + 6 = 21
21 + 7 = 28
28 + 8 = 36
36 + 9 = 45
45 + 10 = 55


Another typical example is to calculate the product of a range of numbers. For example, to calculate the product of 1 to 10 (the factorial of 10):

def product = (1..10).inject(1) { a, b -> a *= b }


The result is the expected 3628800 (= 10!).

4 comments:

jlorenzen said...

I haven't see the use of groupBy. Pretty cool.
What tool are you using to highlight your code in your blog?

Tai Siew Joon said...

jlorenzen: I'm using Alex Gorbatchev's Syntaxhighlighter 2.0. Check it out at http://alexgorbatchev.com

Alexander Malfait said...

Note you can also get the sum of a colletion more clearly like this:

def sum = (1..10).sum { it }

funambule said...

An other example with groupBy, inject() and format() :
[sourcecode language='groovy']// Count the number of items and display them for each creator of more than 2 posts in RSS feeds
Locale.setDefault Locale.ENGLISH // if your computer doesn't speak english
rssFeed = "http://feeds2.feedburner.com/groovyblogs".toURL().text // my prefered Groovy RSS
rssXML = new XmlSlurper().parseText(rssFeed) // get data
// transform rssXML into a 'table' (i.e. list of maps) with inject()
rssTable = rssXML.channel.item.inject([]) { allItems, eachItem ->
allItems << eachItem.children().inject([:]) { record, element ->
record += [(element.name()):(element.toString())]
}
}
// group by creator
groupedByCreator = rssTable.groupBy { it.creator }
// select creators who have posted more than two times
moreThanTwo = groupedByCreator.findAll { group -> group.value.size() > 2 }
// display what you want from each group
moreThanTwo.each { group ->
println group.key.padRight(70,'.') + group.value.size() + ' items'
group.value.each { post ->
println " ${Date.parse('EEE, d MMM yyyy HH:mm:ss Z',post.pubDate).format('dd/MM/yy')} : ${post.title} (${post.link})"
}
}[/sourcecode]