Acme Sprockets

LinkedInTwitterRSS

Array/Enumerable manipulation is SO easy with Ruby

I recently wrote a program to score two different race series for my running club. Of course Ruby was a natural fit because it makes processing the textual data and arrays of scores so easy. I love it!

The problem is fairly straight forward for computer data processing, though it would be tedious by hand. The rules include the following:

  1. The Roads Championship Series will consist of eleven races. Your best six races will count. You must run a minimum of six races in the series to qualify.
  2. You must be an MCRRC member on the day of the race to earn Championship Series points for that race. Non-members are not counted in determining series points.
  3. Your age group for the year will be determined by your age on January first of each year.
  4. In each race points will be awarded to the top ten places overall and to the top ten within age groups. The first-place runner will receive ten points, the second-place runner will receive nine points, and so on down to the tenth-place runner who will receive one point. Points will be accumulated for your best six races at the end of the year.
  5. In case of a tie, the winner will be determined by the time differences in head-to-head competition.

Skipping ahead to the part of the problem where a member's total score withing a division (age group or overall) needs to be determined, imagine a SeriesRunner class with an @race_point variable that is an array of points (0-10) that member may have earned for a particular race. Or nil if they didn't run it at all.

irb(main):001:0> @race_points = [ nil, 9, 8, nil, 6, 7, nil, 8, 10, 6, 10]
=> [nil, 9, 8, nil, 6, 7, nil, 8, 10, 6, 10]

Counting the races run is simple:

irb(main):002:0> @race_points.compact.count
=> 8

And scoring them is pretty easy too. Essentially we need to add up the the best N results ordered from greatest to least:

irb(main):003:0> @race_points.compact.sort.reverse.slice(0...6).inject(:+)
=> 52

Let's break that down and process it piece-by-piece to see how it works.

First, get rid of the nils (races not run):

irb(main):004:0> a=@race_points.compact
=> [9, 8, 6, 7, 8, 10, 6, 10]

Then, sort them largest to smallest:

irb(main):005:0> b=a.sort
=> [6, 6, 7, 8, 8, 9, 10, 10]
irb(main):006:0> c=b.reverse
=> [10, 10, 9, 8, 8, 7, 6, 6]

Then, take the largest n for the number of races that count (6 in this case):

irb(main):007:0> d=c.slice(0...6)
=> [10, 10, 9, 8, 8, 7]
irb(main):008:0> # but what if the intended slice is bigger?
irb(main):009:0* dd=c.slice(0...60)
=> [10, 10, 9, 8, 8, 7, 6, 6]

Finally, add them up:

irb(main):010:0> e=d.inject(:+)
=> 52

See? Super-easy. It took far longer to write this explanation than to program it.

If you're    ready for a zombie apocalypse, then you're ready for any emergency.    emergency.cdc.gov