Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Convert sorted Ruby array to ranks with possible repeats

Tags:

arrays

ruby

I have the the following array of numbers in Ruby (higher is better), and I'd like to rank them. In other words, I want to convert the following sorted list:

[89 52 52 36 18 18 5]

to the following ranks:

[1 2 2 4 5 5 7]

For instance, the winner gets first place, there's a tie for second place, and so on. The important point, obviously, is that ties are possible and those ties must then skip the corresponding ranks. Any number of ties might be possible (3 people sharing second place).

Is there an elegant way to perform this kind of operation?

like image 807
aardvarkk Avatar asked Jul 30 '14 16:07

aardvarkk


4 Answers

Using Enumerable#group_by:

a = [89, 52, 52, 36, 18, 18, 5]

rank = 1
a.group_by {|x| x}.map { |k,v|
  ret = [rank] * v.size
  rank += v.size
  ret
}.flatten
# => [1, 2, 2, 4, 5, 5, 7]

UPDATE

rank, i = 1, 0
a.map { |x|
  i += 1
  x != a[i-2] ? rank = i : rank
}
# => [1, 2, 2, 4, 5, 5, 7]
like image 97
falsetru Avatar answered Nov 11 '22 12:11

falsetru


Using Enumerable#each_with_index to avoid going through the array for each iteration :

a = [89, 52, 52, 36, 18, 18, 5]
rank = 1
a.each_with_index.map{|value, i| a[i-1] == value ? rank : rank = i+1}

Edit : I guess I should also benchmark mine, here are the results

Calculating -------------------------------------
            falsetru       344 i/100ms
                sawa       436 i/100ms
              Jordan      1174 i/100ms
              Iceman        46 i/100ms
        simongarnier       710 i/100ms
-------------------------------------------------
            falsetru     3411.0 (±3.7%) i/s -      17200 in   5.049500s
                sawa     4437.4 (±3.4%) i/s -      22236 in   5.017073s
              Jordan    11746.5 (±2.3%) i/s -      59874 in   5.099797s
              Iceman      463.4 (±2.4%) i/s -       2346 in   5.065717s
        simongarnier     7442.1 (±3.2%) i/s -      37630 in   5.061725s

Not The best one, but still putting up a good fight!

like image 30
simongarnier Avatar answered Nov 11 '22 11:11

simongarnier


a = [89, 52, 52, 36, 18, 18, 5]
a.map{ |e| a.index(e) + 1 }
# => [1, 2, 2, 4, 5, 5, 7]

Edit:

Benchmark from @Jordan gist (https://gist.github.com/jrunning/488de3a19428b9ebb488)

Calculating -------------------------------------
            falsetru       419 i/100ms
                sawa       514 i/100ms
              Jordan      1438 i/100ms
              Iceman        57 i/100ms
-------------------------------------------------
            falsetru     4232.8 (±2.5%) i/s -      21369 in   5.051639s
                sawa     5032.0 (±3.5%) i/s -      25186 in   5.011681s
              Jordan    15057.5 (±2.7%) i/s -      76214 in   5.065319s
              Iceman      575.7 (±1.9%) i/s -       2907 in   5.051481s
like image 5
Eyeslandic Avatar answered Nov 11 '22 12:11

Eyeslandic


I don't know about "elegant," but here's a short, readable, super-straightforward solution:

# assume a pre-sorted non-sparse array
arr = [89, 52, 52, 36, 18, 18, 18, 18, 7]

run = rank = 0
last_n = nil

ranked = arr.map do |n|
  run += 1
  next rank if n == last_n
  last_n = n
  rank += run
  run = 0
  rank
end

p ranked
# => [1, 2, 2, 4, 5, 5, 5, 5, 9]

Benchmarked

I guess we're doing this...

Edit: This post was getting way too long, so I've moved the benchmark code to a Gist: https://gist.github.com/jrunning/8549666d32a6bfa88e41

Here are the results:

      falsetru      730.9 (±2.1%) i/s -       3672 in   5.025755s
  falsetru (2)     1289.9 (±2.7%) i/s -       6500 in   5.042749s
          sawa      986.9 (±2.1%) i/s -       5000 in   5.068450s
        Jordan     1644.9 (±1.9%) i/s -       8250 in   5.017334s
        Iceman        6.4 (±0.0%) i/s -         32 in   5.035015s
  simongarnier     1053.9 (±1.9%) i/s -       5304 in   5.034452s
Cary Swoveland      511.4 (±3.5%) i/s -       2600 in   5.090605s

Edit: I had some stuff in here about Enumerator::Lazy, but it turns out I was using it wrong. It actually didn't improve performance in any case.

like image 4
Jordan Running Avatar answered Nov 11 '22 11:11

Jordan Running