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"]
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"]
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With