Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to "uniq" an array, keeping the last duplicate of each element instead of the first?

Tags:

ruby

When Array#uniq is called, the new array keeps the first occurrence of each duplicate:

["a", "b", "c", "a"].uniq #=> ["a", "b", "c"]

Does the standard library provide a clean way to "uniq" an array, but keep the last occurrence of duplicate elements?

e.g.:

["b", "c", "a"]
like image 253
Thomas Profitt Avatar asked Oct 19 '25 13:10

Thomas Profitt


2 Answers

You can accomplish this by reversing the array, uniquing it, and then reversing it again to the original order:

["a", "b", "c", "a"].reverse.uniq.reverse
#=> ["b", "c", "a"]
like image 122
Gavin Miller Avatar answered Oct 22 '25 04:10

Gavin Miller


This is another way:

require 'set'

def reverse_uniq(arr)
  s = Set.new
  arr.reverse_each.with_object([]) { |e,a| a.unshift(e) if s.add?(e) }
end

For example:

reverse_uniq [1,2,3,4,3,2,1]
  #=> [4, 3, 2, 1] 

Let's compare methods for speed, for a large array:

require 'fruity'
require 'set'

arr = Array.new(1e6) { rand(1e5) }

compare do 
  re     { s = Set.new; arr.reverse_each.with_object([]) { |e,a|
             a.unshift(e) if s.add?(e) } }
  rur    { arr.reverse.uniq.reverse }
  rurb   { arr.reverse.uniq.reverse! }
  rubrb  { (arr.unshift(arr.first)).reverse.uniq!.reverse! }
  rbubrb { (arr << arr[-1]).reverse!.uniq!.reverse! }
end

Running each test once. Test will take about 27 seconds.
rur is similar to rurb
rurb is similar to rubrb
rubrb is similar to rbubrb
rbubrb is faster than re by 30.000000000000004% ± 10.0%

The first bits in rubrb and rbubrb are to ensure: 1) uniq! does not return nil when arr==arr.uniq; and 2) the elements of the array returned are ordered as they are in the array returned by rur.

So, if memory is limited, you might want to use rbubrb if you can mutate the array or rubrb if you can't, else just use rur.

like image 28
Cary Swoveland Avatar answered Oct 22 '25 05:10

Cary Swoveland



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!