views:

163

answers:

3

I'm building a widget to show medal counts for the Olympics. I have a collection of "country" objects, where each has a "name" attribute, and "gold", "silver", "bronze" for medal counts.

List should be sorted: 1. First by total medal count 2. If same medals, sub-sort by type (gold > silver > bronze, ie. two golds > 1 gold + 1 silver) 3. If same medals and type, sub-sort alphabetically

I'm doing this in ruby, but I suppose the language doesn't matter. I did figure out a solution, but if feels like there must be a much more elegant way to do it.

Here's what I did:

  1. Create a virtual attribute with the weighted medal total. So if they had 2 gold and 1 silver, weighted total would be "3.020100". 1 gold and 1 silver and 1 bronze would be "3.010101"

  2. Since we want to sort the medal count as highest first, list is sorted DESC. But then we want to sub-sort alphabetically (ie. ASC) after that. So I created a function which would alpha-invert a word (ie. "canada" => "xzmzwz")

  3. Convert the weighted total to a string, concat the reversed name (ie. "3010101xzmzwz"), then sort descending. Voila.

By now, someone has figured out how to do the same thing in about 2 lines of code. Care to enlighten me?

+6  A: 
countries.sort_by do |country|
  medals = country.gold + country.silver + country.bronze
  [-medals, -country.gold, -country.silver, country.name]
end
sepp2k
Is this specific to a certain Ruby version? It doesn't seem to work for me.
Beanish
It shouldn't be. It works for me on 1.8.7 and 1.9 and I don't see any reason why it shouldn't work on 1.8.6. `sort_by` definitely exists in 1.8.6 and so does `Array#<=>`.
sepp2k
Note that `sort_by` does not sort in-place.
sepp2k
+1 got it working, I like this a lot. Thanks :)
Beanish
+1  A: 

A simple method is to use sort_by with some arbitrary formatted string, like:

countries.sort_by do |c|
  "%010d-%010d-%010d-%s" % [ c.gold, c.silver, c.bronze, c.name ]
end

This converts all the countries in to an ASCII sortable listing by padding the number of medals won to the presumably outrageous 10 places. If anyone wins more than ten billion medals your program may malfunction, but that seems like a reasonable constraint.

tadman
+1 for "reasonable constraint" :-) However, be aware that reasonable constraints can sometimes become unreasonable due to circumstances entirely out of your control: http://Blog.BusinessOfSoftware.Org/2009/01/bos-digest---when-good-assumptions-go-bad.html
Jörg W Mittag
Only in Zimbabwe would you need a Bignum to represent your bank account balance.
tadman
I don't believe this solves one of my original requirements-- the medal count should be sorted descending (largest first), but the alphabetical sub-sort should be ascending (smallest, or a..z).Therefore, sorting by "<gold>-<silver>-<bronze>-<name>" will sort smallest number of medals first. Largest medals first, then alphabetical sub-sort for countries with same no. of medals.
dave
Could easily flip it by inverting the numerical counts, but this is going into deep hack territory compared with the earlier solution. "%010d-%010d-%010d-%s" % [ 10_000_000_000 - c.gold, 10_000_000_000 - c.silver, 10_000_000_000 - c.bronze, c.name ]
tadman
A: 

In Java you would implement comparable on your object and then it could easily be sorted in an ArrayList or Array. Is there a mechanism in Ruby to tell how to compare two of your "Country" objects?

TheSteve0