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"}]

      

+2


source to share


6 answers


This already works for me in 1.8.7.



1:~$ ruby -v
ruby 1.8.7 (2008-08-11 patchlevel 72) [i486-linux]
1:~$ irb -v
irb 0.9.5(05/04/13)
1:~$ irb
>> [{"three"=>"3"}, {"three"=>"4"}, {"three"=>"3"}].uniq 
=> [{"three"=>"3"}, {"three"=>"4"}]

      

+4


source


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"]

      

+4


source


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.

+2


source


How about this?

h={}
[{"three"=>"3"}, {"three"=>"4"}, {"three"=>"3"}].select {|e| need=!h.key?(e) ; h[e]=1 ; need} 
#=> [{"three"=>"3"}, {"three"=>"4"}]

      

+1


source


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.

+1


source


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.

0


source







All Articles