Convert sorted Ruby array to rows with possible repetitions

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

[89 52 52 36 18 18 5] 

for the following ranks:

 [1 2 2 4 5 5 7] 

For example, the winner gets first place, there is a tie in second place, and so on. Obviously, the important point is that connections are possible, and these connections should then skip the corresponding rows. Any number of connections is possible (3 people share second place).

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

+7
arrays ruby
source share
6 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] 
+11
source share

Using Enumerable#each_with_index to avoid passing an 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 think I should also compare 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, but still a good fight!

+6
source share
 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 
+5
source share

I don't know about the "elegant", but here's a short, easy to read, super-direct 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] 

tested

I think we do it ...

Edit: This post has too much time, so I moved the test code to 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 something here about Enumerator :: Lazy , but it turned out that I used it incorrectly. In any case, this did not improve performance.

+4
source share
 a = [89, 52, 52, 36, 18, 18, 5] a.group_by{|e| e} .each_with_object([]){|(_, v), a| a.concat([a.length + 1] * v.length)} # => [1, 2, 2, 4, 5, 5, 7] 

 require "ips" @a = [89, 52, 52, 36, 18, 18, 5] def method1 rank = 1 @a.group_by {|x| x}.map { |k,v| ret = [rank] * v.size rank += v.size ret }.flatten end def method2 @a.group_by{|e| e} .each_with_object([]){|(_, v), a| a.concat([a.length + 1] * v.length)} end Benchmark.ips do |b| b.report{method1} b.report{method2} end Calculating ------------------------------------- 8109 i/100ms 10160 i/100ms ------------------------------------------------- 99329.78.1%) i/s - 494649 in 5.018516s 137445.05.0%) i/s - 690880 in 5.040186s 
+2
source share

Another way that uses Enumerated # chunk .

the code

 def ranks(a) a.each_with_index .chunk { |e,_| e } .flat_map { |_,a| [a.first.last+1]*a.size } end 

Example

 a = [89, 52, 52, 36, 18, 18, 5] ranks(a) #=> [1, 2, 2, 4, 5, 5, 7] 

Explanation

For array a above:

 b = a.each_with_index #=> #<Enumerator: [89, 52, 52, 36, 18, 18, 5]:each_with_index> b.to_a # values to be passed to block #=> [[89, 0], [52, 1], [52, 2], [36, 3], [18, 4], [18, 5], [5, 6]] c = b.chunk { |e,_| e } #=> #<Enumerator: #<Enumerator::Generator:0x000001010e39e8>:each> c.to_a # values to be passed to block #=> [[89, [[89, 0]]], # [52, [[52, 1], [52, 2]]], # [36, [[36, 3]]], # [18, [[18, 4], [18, 5]]], # [5, [[5, 6]]]] d = c.flat_map { |_,a| [a.first.last+1]*a.size } #=> [1, 2, 2, 4, 5, 5, 7] 
+1
source share

All Articles