Extending uniq method
This is Ruby 1.8 question:
We all know how to use Array#uniq
:
[1,2,3,1].uniq #=> [1,2,3]
However, I'm wondering if we can defuse it to work with complex objects. The current behavior looks like this:
[{"three"=>"3"}, {"three"=>"4"}, {"three"=>"3"}].uniq
#=> [{"three"=>"3"}, {"three"=>"4"}, {"three"=>"3"}]
Requested request:
[{"three"=>"3"}, {"three"=>"4"}, {"three"=>"3"}].uniq
#=> [{"three"=>"3"}, {"three"=>"4"}]
source to share
To make Array # uniq work for any object, you must override two methods: hash and eql?
All objects have a hash method that calculates the hash value for that object, so for two objects equal to their values ββwhen the hash must also be equal.
Example. A user is unique when their email address is unique:
class User
attr_accessor :name,:email
def hash
@email.hash
end
def eql?(o)
@email == o.email
end
end
>> [User.new('Erin Smith','roo@example.com'),User.new('E. Smith','roo@example.com')].uniq
=> [#<User:0x1015a97e8 @name="Erin Smith", @email="maynurd@example.com"]
source to share
The problem is that Hash#hash
, and Hash#eql?
both give bogus results in Ruby 1.8.6. This is one of the very rare monkey patches that I'm willing to run because this bug seriously breaks a lot of code - in particular, memoizing functions. Just be careful with patches of monkeys that you do not overcome no disturbed behavior.
So:
class Hash
if {}.hash != {}.hash
def hash
# code goes here
end
end
if !{}.eql?({})
def eql?(other)
# code goes here
end
end
end
But if you are doing something where you manage the deployment environment, just raise an error if the application starts running 1.8.6.
source to share
I've encountered this on my own many times. Hash uniformity is broken in Ruby 1.8.6:
require 'test/unit'
class TestHashEquality < Test::Unit::TestCase
def test_that_an_empty_Hash_is_equal_to_another_empty_Hash
assert({}.eql?({}), 'Empty Hashes should be eql.')
end
end
Passes in Ruby 1.9 and Ruby 1.8.7, does not work in Ruby 1.8.6.
source to share
1.8.7 :039 > [{"three"=>"3"}, {"three"=>"4"}, {"three"=>"3"}].uniq {|x|x.values}
=> [{"three"=>"3"}, {"three"=>"4"}]
1.8.7 :040 > [{"three"=>"3"}, {"three"=>"4"}, {"three"=>"3"}].uniq {|x|x.keys}
=> [{"three"=>"3"}]
How about something like this? just uniq_by hash value or hash key across a block.
source to share