Using collect on Groovy collections

When you find something you like, you want to share it with the world. Here are some more tricks when working with collections in Groovy that makes programming fun again! ;)

Let’s get a sample model and some data first, then we’ll get to the fun and easy Grooviness…

Let’s create some rock stars!
class RockStar {
def name
def bands
def numberOfGroupies
}

def mike = new RockStar(name:"Mike Patton",
bands:["Faith No More","Mr. Bungle"],
numberOfGroupies:2214)
def bono = new RockStar(name:”Bono”,
bands:["U2"],
numberOfGroupies:543582)
def slash = new RockStar(name:”Saul Hudson”,
bands:["Guns N Roses", "Velvet Revolver"],
numberOfGroupies:32544)
def scott = new RockStar(name:”Scott Weiland”,
bands:["Stone Temple Pilots", "Velvet Revolver"],
numberOfGroupies:41880)

def rockStarList = [mike, bono, slash, scott]

As you can see, I have created a simple POGO class, loaded some beans, then tossed them into a simple list.

Now what? Well, maybe we need to make a list of all the bands our rock stars are a part of, or better yet, a set. We don’t want the same band listed twice now! So what is the easiest way to make this happen? Let me introduce you to a very useful method in your Groovy toolbox, collect.

Collect all the bands from the rock stars
Set allBands = rockStarList.collect { rockStar ->
rockStar.bands
}

The closure you pass to the collect method is used to transform that list item into whatever you want. In this case, I’m taking a list of RockStars and transforming it into a set of string lists, containing band names. When you run this, you will get a Set that contains the Lists within the objects. This is really useful when you have a list of domain classes that you don’t really need, but you need to get at some information inside each of them.

[["Faith No More", "Mr. Bungle"], ["Stone Temple Pilots", "Velvet Revolver"], ["Guns N Roses", "Velvet Revolver"], ["U2"]]

But this is still no good! We want a List of Strings that contain band names, not a List of Lists! But wait! Groovy makes this dead-easy to fix:

Flatten the bands
Set allBands = rockStarList.collect { rockStar ->
rockStar.bands
}.flatten()

Using the flatten list method does exactly what we want. It takes all the collections within the top-level collection and adds them recursively to the top-level collection, effectively flattening them all out into one collection.

["Velvet Revolver", "Stone Temple Pilots", "U2", "Faith No More", "Guns N Roses", "Mr. Bungle"]

Perfect. But you want to know the total number of groupies for every rock star? No problem.

Count up the groupies!
def totalGroupies = rockStarList.collect {
it.numberOfGroupies
}.sum()
If you don’t want to explicitly define the closure parameter for a closure, groovy will default to “it”. This makes it easy to keep the characters you type down to a minimum.

Before we call the sum() method, our closure returns a List containing Integer objects. If you call sum() on a list of any objects, this effectively calls the “plus()” method on each of the objects. Because the list is loaded with Integer objects, these all add up nicely.

Try runnig 1.plus(2) in groovy. Then run 1 + 2. Same answer. The “+” operator is resolved to the “plus()” method on whatever operand it operates on (which is an Integer object, in this case). This happens anywhere in groovy, and with more than just “+”.

And there you go:

620220

There will be 620,220 groupies at the show.

This entry was posted in uncategorized and tagged . Bookmark the permalink. Post a comment or leave a trackback: Trackback URL.

9 Comments

  1. Rakesh
    Posted March 30, 2008 at 4:50 am | Permalink

    This looks very handy, thanks for the well explained example

  2. Posted March 30, 2008 at 11:54 pm | Permalink

    For your last example, totaling the number of groupies, I like to use the inject method to pass a result from one iteration through a list to the next:

    def totalGroupies = rockStarList.inject(0) { groupies, rockStar -> groupies + rockStar.numberOfGroupies }

    You inject the initial starting value and then the result returned from each iteration is passed to the next in the first parameter to the closure (i.e. groupies).

    It avoids gathering together a list which is then iterated over again to do the sum.

  3. Posted March 31, 2008 at 5:57 am | Permalink

    Ted, thanks for the tip! When I was trying to find a way to get the total number of groupies, that’s exactly what I wanted to do. I just didn’t know about the inject method. Awesome.

  4. Bryan Bunch
    Posted March 31, 2008 at 9:57 am | Permalink

    Very cool — groovy closures are one of the biggest time savers I’ve run across. Also, the “sum” method has an overloaded version that also takes a closure as a single argument — this means you can also write:

    def totalGroupies = rockStarList.sum {
        it.numberOfGroupies
    }

    and you’ll get the same result. I love groovy — I frequently have database investigation tasks that need to be run for the company I work for, and the groovy.sql.Sql class makes short work of these. My next goal now is to try and play with Grails a bit — if it really lives up to all the hype, I’m really looking forward to having fun again with web development.

  5. Posted March 31, 2008 at 10:02 am | Permalink

    @Bryan: I’m working on a grails project right now, and yes, it is making web development fun again :)

  6. Posted April 1, 2008 at 2:46 pm | Permalink

    It’s also possible to simply use the so-called spread operator *. as a handy short cut:

    Set s = rockStarList*.bands.flatten()

    See: http://groovy.codehaus.org/Operators#Operators-SpreadOperator%28.%29

  7. Dan Lewis
    Posted April 3, 2008 at 7:50 pm | Permalink
    import com.google.common.collect.Lists;
    import com.google.common.collect.Sets;
    
    import java.util.List;
    import java.util.Set;
    
    public class GroovySmackdown {
        public static void main(String[] args) {
            class RockStar {
                String name;
                Set<String> bands;
                int numGroupies;
            }
    
            RockStar mike = new RockStar();
            mike.name = "Mike Patton";
            mike.bands = Sets.newHashSet("Faith No More", "Mr. Bungle");
            mike.numGroupies = 2214;
    
            RockStar bono = new RockStar();
            bono.name = "Bono";
            bono.bands = Sets.newHashSet("U2");
            bono.numGroupies = 543582;
    
            RockStar slash = new RockStar();
            slash.name = "Saul Hudson";
            slash.bands = Sets.newHashSet("Guns N Roses", "Velvet Revolver");
            slash.numGroupies = 32544;
    
            RockStar scott = new RockStar();
            scott.name = "Scott Weiland";
            scott.bands = Sets.newHashSet("Stone Template Pilots", "Velvet Revolver");
            scott.numGroupies = 41880;
    
            List<RockStar> rockstars = Lists.newArrayList(mike, bono, slash, scott);
    
            // Q: What are all the bands our rock stars are part of?
            Set<String> allBands = Sets.newHashSet();
            for (RockStar rockstar : rockstars) {
                allBands.addAll(rockstar.bands);
            }
    
            // Q: What are the total number of groupies for every rock star?
            int numGroupies = 0;
            for (RockStar rockstar : rockstars) {
                numGroupies += rockstar.numGroupies;
            }
    
            System.out.format("There will be %d groupies at the show.", numGroupies);
        }
    }
  8. Posted April 4, 2008 at 9:45 am | Permalink

    In response to Dan’s reply, I have challenged him to a code-off!!

    I will be posting some groovy collections code on my blog, starting today, and Dan will respond on his blog with some Google collections magic using straight Java.

    Sure to be a good time!

  9. Posted April 15, 2008 at 12:24 pm | Permalink

    These methods are very usefull

One Trackback

  1. [...] dangertree techblog sweaty programming and disconnected gibberish « Using collect on Groovy collections [...]

Post a Comment

Your email is never published nor shared. Required fields are marked *

*
*
Check out the latest GroovyMag to see an interview with me about the 1.1 release of GrailsUI: