Groovy vs. Google Collections: Round #2

For Round #2 of our code challenge, Dan has responded to my initial provocation in spades with a efficient example of Google CollectionsMultiMap utilities. His model involves car makes and models, their associations, and the easiest way to travel between them. At the core of the problem is the fact that a make can have many models, and we want an easy collection that will have utilities to get the data from both ends. Touché, my good man, because groovy doesn’t have a utility that matches the MultiMap functionality.

In order to quickly emulate the same data structure, I was forced to push a bunch of single-element maps into a list! Yuck!

Setting up my collection
def cars = []
cars << ['Ford':'Taurus'] << ['Ford':'Focus'] << ['Ford':'Mustang']
cars << ['Chevrolet':'Malibu'] << ['Chevrolet':'Impala'] << ['Chevrolet':'Corvette']
cars << ['Dodge':'Charger'] << ['Dodge':'Avenger'] << ['Dodge':'Viper']

But at least I have something I can work with now, albeit not the optimal collection that MultiMap would have provided in this case.

The real logic
def printModels = { make, pair ->
    if (pair.any{it.key == make})
        println "$make makes ${(pair.values() as List)[0]}"
    make
}
def printMake = { model, pair ->
    if (pair.any{it.value == model})
        println "$model is made by ${(pair.keySet() as List)[0]}"
    model
}
cars.inject('Ford', printModels)
cars.inject('Chevrolet', printModels)
cars.inject('Dodge', printModels)
cars.inject('Impala', printMake)

All the work is done in the closures. If I had the MultiMap’s capabilities, I could have created a map that would store multiple values for each key (see Dan’s initial volley), and I could have just called get() to retrieve a set of those values. But my groovy closures are looking specifically for either the key or value in the list of single-element maps. The closures are meant to be used with the inject method of groovy collections. The first printModels closure expects the make to be injected into it, and then compares it to each map key to find whether the current pair should be printed out. The printMakes closure expects the model to be passed, comparing it to the pair values.

I was disappointed to find out that groovy’s map implementation had no inverse operation. In java, this is not possible, because a key can have only one value, and if you try to inverse, you might possibly have a key with many values, breaking java into tiny pieces. Because the MultiMap allows keys to have multiple values, there is no danger in inversing the positions of keys and values, which is very handy (again, see how Dan used it)

The calls to inject actually execute the closures that print out the strings. The first three print out the models for each make injected into the closure, and the last one injects a model and prints out its make.

So there are some pluses, some minuses to using groovy for this sort of problem. On one hand, my total lines of code are still under 20, which is less than Dan’s example. However, I’m using a Frankensteined collection (a list of single-element maps). Can we get the best of both worlds somehow?

You can add any java library to your groovy project, just like it was a java project.

So let’s add Google’s Collections package as see if we can use it with groovy to make our code nicer!

Groovy and Google’s collections hand-in-hand
import com.google.common.collect.*

def cars = Multimaps.newHashMultimap()
cars.put('Ford','Taurus')

Ok… let’s stop right there. The groovy programmer in me really wanted to add to the map using cars.’Ford’ = ‘Taurus’, but no dice. This isn’t a groovy map, it is a google map, so I’ve lost all my groovy functionality, which isn’t very groovy. And my script from this point on is going to look suspiciously like Dan’s java program. So what is the point of this experiment? Well, it lets me know that if I run into a situation where I’ll need the functionality of a MultiMap, I can easily import the google jar and whip out an inverse-able MultiMap collection. Only, I won’t be able to use all the sugary groovy syntax with it. It is still better to have the option.

In summary, I think the google java collections package is downright cool. Anything that makes programming simpler and easier is good for our business. Groovy is great, but if I were working in java today, I would love this package. Hell, I might even want to use it with groovy someday. Thanks to Dan Lewis for participating in my challenge and being a good sport.


8 Responses to “Groovy vs. Google Collections: Round #2”

  1. Dave Klein Says:

    Remember the old Reese’s Peanut Butter Cup commercials? Here’s a new version:

    Dan: “You got Groovy on my Google Collections!”

    Matt: “You got Google Collections in my Groovy!”

    Dan and Matt in unison: “Hey… mmm…”

    Announcer: “Groovy and Google Collections… Two great techs that work great together!”

    If I had a TV I’d be watching for that one to show up soon. But seriously. Thanks to you and Dan both for these posts.

  2. Ted Naleid Says:

    Groovy doesn’t come with an inverse or a MultiMap “put” built in, but because of the dynamic nature of groovy, they are really easy to add.

    Here’s an example of groovy code that emulates what the java code is doing (creating the same types of hashmaps as well as defining the same variable names and comments):

    LinkedHashMap.metaClass.multiPut << { key, value ->
        delegate[key] = delegate[key] ?: []; delegate[key] += value
    }
    LinkedHashMap.metaClass.multiInvert << { ->
        delegate.inject([:]) { newMap, entry ->
            entry.value.each { value -> newMap.multiPut(value, entry.key) }
            return newMap
        }
    }
    LinkedHashMap makeToModel = [:]
    ["Taurus", "Focus", "Mustang"].each { makeToModel.multiPut("Ford", it) }
    ["Malibu", "Impala", "Corvette"].each { makeToModel.multiPut("Chevrolet", it) }
    ["Charger", "Avenger", "Viper"].each {  makeToModel.multiPut("Dodge", it) }
    //forward search
    makeToModel.keySet().each { make ->
        println "$make makes ${makeToModel[make].join(', ')}"
    }
    // Invert the map
    LinkedHashMap modelToMake = makeToModel.multiInvert()
    //reverse search
    def searchModel = "Impala"
    println "model $searchModel is made by ${modelToMake.get(searchModel)}"

    (pastie for code here: http://pastie.caboo.se/177973)

    And it outputs what I believe is the same thing as the original Java code:

    Ford makes Taurus, Focus, Mustang
    Chevrolet makes Malibu, Impala, Corvette
    Dodge makes Charger, Avenger, Viper
    model Impala is made by [”Chevrolet”]

  3. Matthew Taylor Says:

    @Ted You kick ass! Thanks for continuing with my example where I lost steam. That is exactly what I wish I had the drive and foresight to do last night at 10PM :)

  4. Ted Naleid Says:

    Thanks Matthew, I’m glad you liked it :).

    One note..I think some of my less than signs got eaten by the blog in the reformat of my code above (mostly the defining the multiPut and multiInvert methods), but the pastie link has the correct code.

  5. Matthew Taylor Says:

    @Ted I think I got it. For pasting code into blogs, I found this quick escape tool that helps convert text into escaped text. Very handy for things like this. I think I’ll put a note about it above the comments block…

  6. Michael Easter Says:

    (a) This thread is really enjoyable. Thanks Matt and Dan

    (b) Ted’s solution is delightful. I was wondering if there was some meta stuff that could work.

    (c) I’m not sure that it is “easy” to add (re: Ted). Ted’s solution is fairly sophisticated, esp. for something as useful as a multi-map. I wonder if a true multimap will be added to the GDK?

  7. Eugene Kuleshov Says:

    I was wondering how long would it take for someone to write an utility class that would allow to create maps like this:

    Map cars = MapBuilder.put(”Ford”, “Taurus’).put(”Ford”, “Focus”).put(”Ford”, “Mustang”).put(”Chevrolet”, “Malibu”).put(”Chevrolet”, “Impala”).put(”Chevrolet”, “Corvette”).put(”Dodge”, “Charger”).put(”Dodge”, “Avenger”).put(”Dodge”, “Viper”).toMap();

    Though this example is quite useless in real life.

  8. Matthew Taylor Says:

    @Michael: in regards you part (c) of your comment… I think I’m going to add a JIRA issue to groovy about this. Maybe even two. I’d like to see (1) an inverse method on any groovy map that strips off any duplicate keys that arise, (2) a multi-map implementation.

    @Eugene: Are you talking about a java utility class? I would be more interested in a groovy implementation that would work like this:

    def multimap = [Ford:'Taurus', Ford:'Focus', Chevy:'Malibu'] as MultiMap
    println multimap.values(Ford)

    should print

    [’Taurus’, ‘Focus’]

Leave a Reply