views:

183

answers:

3

I have a short groovy algorithm for assigning rankings to food based on their rating. This can be run in the groovy console. The code works perfectly, but I'm wondering if there is a more Groovy or functional way of writing the code. Thinking it would be nice to get rid of the previousItem and rank local variables if possible.

def food = [
  [name:'Chocolate Brownie',rating:5.5, rank:null],
  [name:'Fudge', rating:2.1, rank:null],
  [name:'Pizza',rating:3.4, rank:null],
  [name:'Icecream', rating:2.1, rank:null],
  [name:'Cabbage', rating:1.4, rank:null]]

food.sort { -it.rating }

def previousItem = food[0]
def rank = 1
previousItem.rank = rank
food.each { item ->
  if (item.rating == previousItem.rating) {
    item.rank = previousItem.rank
  } else {
    item.rank = rank
  }
  previousItem = item
  rank++
}

assert food[0].rank == 1
assert food[1].rank == 2
assert food[2].rank == 3
assert food[3].rank == 3    // Note same rating = same rank
assert food[4].rank == 5    // Note, 4 skipped as we have two at rank 3

Suggestions?

A: 

I didn't try it, but maybe this could work.

food.eachWithIndex { item, i ->
    if( i>0 && food[i-1].rating == item.rating ) 
        item.rank = food[i-1].rank
    else
        item.rank = i + 1
}
Daniel Engmann
Thanks Daniel, the index based approach seems clean, inlining the sort { -it.rating } call makes it fit with my 'no local defs' requirement.
Richard Paul
+1  A: 

This is my solution:

def rank = 1
def groupedByRating = food.groupBy { -it.rating }
groupedByRating.sort().each { rating, items ->
  items.each { it.rank = rank }
  rank += items.size()
}
Christoph Metzendorf
Thanks for the groupBy idea, I'm a fan. However I get a null pointer, I think related to the sort() call.I removed the local variables by inlining the groupBy and injecting the initial rank of 1. food.sort { -it.rating }.groupBy { it.rating }.inject(1) { rank, group -> def items = group.value items.each { it.rank = rank } rank += items.size() }(ugg, how to you format in a comment?)
Richard Paul
It seems that the NullPointerException is a bug of former Groovy versions. I could reproduce it with Groovy 1.7.0. With Groovy 1.7.2 the code works as described.
Christoph Metzendorf
A: 

Here's another alternative that doesn't use "local defs" with the groovy inject method:

food.sort { -it.rating }.inject([index: 0]) { map, current ->
    current.rank = (current.rating == map.lastRating ? map.lastRank : map.index + 1)
    [ lastRating: current.rating, lastRank: current.rank, index: map.index + 1 ]
}
Ted Naleid