Comparing objects

How can I compare two objects and get true if some of his atributes are
equals ?

I need to compare two arrays of users, and get and third array just with
the matches. I found the "&" method that work for Fixnuns and String,
but...how to use with objects ?

class User
attr_accessor :email
end

a = User.new
a.email = 'ruby@rails.com'

b = User.new
b.email = 'ruby@rails.com'

array_one = [a]
array_two = [b]

array_three = array_one & array_two

puts array_three # I want the user here

Can you help me ?
thanks

···

--
Posted via http://www.ruby-forum.com/.

Anderson Leite wrote:

How can I compare two objects and get true if some of his atributes are
equals ?

I need to compare two arrays of users, and get and third array just with
the matches. I found the "&" method that work for Fixnuns and String,
but...how to use with objects ?

class User
attr_accessor :email
end

a = User.new
a.email = 'ruby@rails.com'

b = User.new
b.email = 'ruby@rails.com'

array_one = [a]
array_two = [b]

array_three = array_one & array_two

puts array_three # I want the user here

Can you help me ?
thanks

I think you need to define how to compare your objects and their hash.
Have a look below.

class User
attr_accessor :email

def ==(other)
  @email == other.email
end

alias eql? ==

def hash
    code = 17
    code = 37*code + @email.hash
end

def to_s
  "#@email"
end

end

a = User.new
a.email = 'ruby@rails.com'

c = User.new
c.email = 'groovy@rails.com'

b = User.new
b.email = 'ruby@rails.com'

d = User.new
d.email = 'c++@rails.com'

d = User.new
d.email = 'python@rails.com'

array_one = [a,d]
array_two = [b, c]

array_three = array_one & array_two

puts array_three # I want the user here
#prints ruby@rails.com

···

--
Posted via http://www.ruby-forum.com/\.

First, let's be clear about the semantics of the various comparison methods:

The == operator checks whether two objects are equal (after coercing them to the same type).

1 == 2 # => false
1 == 1.0 # => true

#eql? checks if two objects are equal and of the same type.

1.eql? 1.0 # => false
'a'.eql? 'a' # => true

#equal? checks if two objects are identical (the same object).

'a'.equal? 'a' # => false
:a.equal? :a # => true

As a mnemonic, note that their strictness is a function of their length.

Now, on to the original question, which (paraphrased) is:

"How can I get arrays of custom objects to work as expected with the set intersection operator &?"

The Array#& operator, like its siblings, uses the #hash and #eql? methods of the objects it compares. These must be the same for objects you want to be considered equivalent.

Armed with this knowledge, let's define those methods on User. For instance:

class User

  # checks if objects are equal
  def ==(other)
    @email == other.email
  end

  # checks if objects are equal and the same class
  def eql?(other)
    self == other && self.class == other.class
  end

  # unique hash based on email and class
  # We add 1 to prevent User.new('').hash == User.hash
  # NOTE: Users with @email == nil will have the same hash and modify if necessary.
  def hash
    @email.hash ^ self.class.hash +
  end
end

This will allow:

[User.new('bob@example.com')] & [User.new('bob@example.com')]

=> [#<User:0x1016dcdb8 @email="bob@example.com">]

Which I believe was the original intent. Keep in mind that this will also cause Hash to see them as the same object for keys, which is probably also expected

As an aside, I would also like to comment that the following, from one of the responses:

def to_s
  "#@email"
end

is unnecessarily complex and can be replaced simply with:

def to_s
  @email
end

Don't use string interpolation when you don't need it. If you want to ensure that @email is a string, call to_s on it instead.

···

On 2010-06-03 15:29:55 -0700, Anderson Leite said:

I need to compare two arrays of users, and get and third array just with
the matches. I found the "&" method that work for Fixnuns and String,
but...how to use with objects ?

class User
attr_accessor :email
end

a = User.new
a.email = 'ruby@rails.com'

b = User.new
b.email = 'ruby@rails.com'

array_one = [a]
array_two = [b]

array_three = array_one & array_two

puts array_three # I want the user here

Can you help me ?
thanks

Rein Henrichs

http://reinh.com

include Comparable ?

Regards,

Dan

···

On Jun 3, 4:29 pm, Anderson Leite <anderson...@gmail.com> wrote:

How can I compare two objects and get true if some of his atributes are
equals ?

<snip>

def ==(other)
@email == other.email
end

this seems ok

alias eql? ==

def hash
code = 17
code = 37*code + @email.hash
end

Are you sure you want to do this? "equal" objects would overwrite each
other when used as hash keys. This is normally not a good idea, be
sure you really want/need this!
R

···

On Fri, Jun 4, 2010 at 3:41 AM, Marcin Wolski <wolskint@o2.pl> wrote:

--
The best way to predict the future is to invent it.
-- Alan Kay

Correction:

  def hash
    @email.hash ^ self.class.hash +
  end

should be:

def hash
  @email.hash ^ self.class.hash + 1
end

<snip>

The Array#& operator, like its siblings, uses the #hash and #eql? methods of
the objects it compares. These must be the same for objects you want to be
considered equivalent.

I must have made a stupid mistake in my example code, however, this is
no reason to mess with #eql? and #hash, Be warned.
In that case I stick with Robert's advice: Do not use Array#&, even
MPing Array with something like #intersect_by would be preferable.

Cheers
Robert

···

On Sat, Jun 5, 2010 at 8:55 PM, Rein Henrichs <reinh@reinh.com> wrote:

Robert Dober wrote:

···

On Fri, Jun 4, 2010 at 3:41 AM, Marcin Wolski <wolskint@o2.pl> wrote:
<snip>

�def ==(other)
�@email == other.email
�end

this seems ok

�alias eql? ==

�def hash
� �code = 17
� �code = 37*code + @email.hash
end

Are you sure you want to do this? "equal" objects would overwrite each
other when used as hash keys. This is normally not a good idea, be
sure you really want/need this!
R

What would be the other possible solution to this problem? The example
code will not work without overwritten eql? and hash methods.
--
Posted via http://www.ruby-forum.com/\.

The first solution sent by Marcin Wolski works for normal ruby classes,
but I got something wrong when using with an ActiveRecord class. I am
not sure about the problem know.

So I tried Robert Klemme's solution, using:

in_both = obj_from_db.select do |u1|
   obj_from_xml.any? do |u2|
     u1.email == u2.email && u1.age == u2.age
   end
end

That's working know.
I have to study a little more of your suggestions since this aproach
still looks like slowly yet....

To figure the problem, I am using this algorithm at
http://www.dobbyme.com

That's a birthday reminder. The idea implemented here is: I got your
gmail contacts (useim gdata gem) and lokk at dobbyme's database to match
if you have some contact already registered.

I think now the actual aproach is fine, but with a lot of users I will
start to have some troubles...am I right ? Opnions ?

···

--
Posted via http://www.ruby-forum.com/.

"Be warned"? "Do not use Array#&"? Enough of this baseless FUD. Understand the consequences of what you're doing, certainly, but don't shy away from techniques just because someone else cries wolf.

Array#& is meant to be used. Implementing #hash and #eql? in your classes is as normal as implementing #==, #<=> or #each. Ruby's duck typing is one of its most powerful features and should always be preferred over one-off monkey-patching. I'm afraid Robert has it precisely backwards.

···

On 2010-06-05 13:09:30 -0700, Robert Dober said:

On Sat, Jun 5, 2010 at 8:55 PM, Rein Henrichs <reinh@reinh.com> wrote:
<snip>

The Array#& operator, like its siblings, uses the #hash and #eql? methods of
the objects it compares. These must be the same for objects you want to be
considered equivalent.

I must have made a stupid mistake in my example code, however, this is
no reason to mess with #eql? and #hash, Be warned.
In that case I stick with Robert's advice: Do not use Array#&, even
MPing Array with something like #intersect_by would be preferable.

Cheers
Robert

--
Rein Henrichs

http://reinh.com

Robert Dober wrote:

···

On Sat, Jun 5, 2010 at 8:55 PM, Rein Henrichs <reinh@reinh.com> wrote:
<snip>

The Array#& operator, like its siblings, uses the #hash and #eql? methods of
the objects it compares. These must be the same for objects you want to be
considered equivalent.

I must have made a stupid mistake in my example code, however, this is
no reason to mess with #eql? and #hash, Be warned.
In that case I stick with Robert's advice: Do not use Array#&, even
MPing Array with something like #intersect_by would be preferable.

Wow. Sorry, I am just stunned by this, so no sugar coating for you
today.

Array#& is a perfectly legitimate method to be using for the purpose of
finding the intersect of two Array objects, which it seems what the OP
wanted to do originally.

I agree with Rein on this.

--
Posted via http://www.ruby-forum.com/\.

Implementing this behavior with your own object types is the whole
point of the Ruby core API.
I find myself struggling to understand the mindset that would prefer
re-opening Array and adding something over using the
already-there-for-a-reason Enumerable and Array interfaces.

Using eql? and hash is not 'messing with' them, unless you don't take
the time to understand your tools.

···

On Sat, Jun 5, 2010 at 4:09 PM, Robert Dober <robert.dober@gmail.com> wrote:

I must have made a stupid mistake in my example code, however, this is
no reason to mess with #eql? and #hash, Be warned.
In that case I stick with Robert's advice: Do not use Array#&, even
MPing Array with something like #intersect_by would be preferable.

Does it not? Seems to work on my box
class A
  def == other
    true
  end
end # class A

a1 = A::new
a2 = A::new
a3 = A::new

p [a1,a2] & [a1,a3]

···

On Fri, Jun 4, 2010 at 10:16 AM, Marcin Wolski <wolskint@o2.pl> wrote:

Robert Dober wrote:

On Fri, Jun 4, 2010 at 3:41 AM, Marcin Wolski <wolskint@o2.pl> wrote:
<snip>

�def ==(other)
�@email == other.email
�end

this seems ok

�alias eql? ==

�def hash
� �code = 17
� �code = 37*code + @email.hash
end

Are you sure you want to do this? "equal" objects would overwrite each
other when used as hash keys. This is normally not a good idea, be
sure you really want/need this!
R

What would be the other possible solution to this problem? The example
code will not work without overwritten eql? and hash methods.
--
Posted via http://www.ruby-forum.com/\.

--
The best way to predict the future is to invent it.
-- Alan Kay

Robert Dober wrote:

<snip>

�def ==(other)
�@email == other.email
�end

this seems ok

�alias eql? ==

�def hash
� �code = 17
� �code = 37*code + @email.hash
end

Are you sure you want to do this? "equal" objects would overwrite each
other when used as hash keys. This is normally not a good idea, be
sure you really want/need this!

Absolutely agree!

What would be the other possible solution to this problem?

Actually I am not sure what the problem actually is. Does OP want to
return all objects which are in both Arrays? Does he want to return
all objects which share a particular set of properties? etc. Before
we can provide solutions we have to know what problem must be solved.

Kind regards

robert

···

2010/6/4 Marcin Wolski <wolskint@o2.pl>:

On Fri, Jun 4, 2010 at 3:41 AM, Marcin Wolski <wolskint@o2.pl> wrote:

--
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/

Did I say you should not use Array#&??? I do not think so. I said, do
not overload Object#hash and Object#eql? for the purpose to use
Array#&. And if I was not clear enough I will try again: It will brake
any client code that uses your objects as hash keys.
Thus you override #hash and #eql? if you want that behavior only.
But yes, using Array#& with different object comparison semantics
seems a bad idea to me. Please bear in mind that there is no reason to
assume that OP has the kind of experience to assume the side effects
present (He would not have asked the question in that case).
Cheers
Robert

···

On Sun, Jun 6, 2010 at 1:05 AM, Ryan Bigg <me@ryanbigg.com> wrote:

Robert Dober wrote:

On Sat, Jun 5, 2010 at 8:55 PM, Rein Henrichs <reinh@reinh.com> wrote:
<snip>

The Array#& operator, like its siblings, uses the #hash and #eql? methods of
the objects it compares. These must be the same for objects you want to be
considered equivalent.

I must have made a stupid mistake in my example code, however, this is
no reason to mess with #eql? and #hash, Be warned.
In that case I stick with Robert's advice: Do not use Array#&, even
MPing Array with something like #intersect_by would be preferable.

Wow. Sorry, I am just stunned by this, so no sugar coating for you
today.

Array#& is a perfectly legitimate method to be using for the purpose of
finding the intersect of two Array objects, which it seems what the OP
wanted to do originally.

--
The best way to predict the future is to invent it.
-- Alan Kay

I must have made a stupid mistake in my example code, however, this is
no reason to mess with #eql? and #hash, Be warned.
In that case I stick with Robert's advice: Do not use Array#&, even
MPing Array with something like #intersect_by would be preferable.

Implementing this behavior with your own object types is the whole
point of the Ruby core API.
I find myself struggling to understand the mindset that would prefer
re-opening Array and adding something over using the
already-there-for-a-reason Enumerable and Array interfaces.

Really? What do we want to do? We want Array#& work with a tailor made
comparision.
I accept that attitude, although I would not recommend it.
Now I would expect that the responsibility for changing the semantics
of an Array method should be in the Array class, or a module closely
related to class. As Array#& needs to invoke client object's methods,
we have to change those. And reality is that Hash methods invoke those
same client methods. Thus I would not say that #hash? and #eql? are
already here for the reason you stated.

Using eql? and hash is not 'messing with' them, unless you don't take
the time to understand your tools.

I agree that was bad style from my side, sorry. However I maintain
that the context does not really justify their modifications as the
semantic impact is much greater than wanted, unless of course OP wants
hashes to believe exactly as they would after the aforementioned
modifications.

Cheers
Robert

···

On Sun, Jun 6, 2010 at 1:18 AM, Wilson Bilkovich <wilsonb@gmail.com> wrote:

On Sat, Jun 5, 2010 at 4:09 PM, Robert Dober <robert.dober@gmail.com> wrote:

--
The best way to predict the future is to invent it.
-- Alan Kay

Before

we can provide solutions we have to know what problem must be solved.

Kind regards

robert

I have a list of objects that came from database and another list of
objects extracted from a xml. I need the elements who are in both lists.

Then...I thought to compare objects overriding the == method like Marcin
Wolski wrote. There is another solution ?

···

--
Posted via http://www.ruby-forum.com/\.

Robert Dober wrote:

···

On Sun, Jun 6, 2010 at 1:05 AM, Ryan Bigg <me@ryanbigg.com> wrote:

MPing Array with something like #intersect_by would be preferable.

Wow. Sorry, I am just stunned by this, so no sugar coating for you
today.

Array#& is a perfectly legitimate method to be using for the purpose of
finding the intersect of two Array objects, which it seems what the OP
wanted to do originally.

Did I say you should not use Array#&??? I do not think so.

Let me refresh your memory:

"In that case I stick with Robert's advice: Do not use Array#&,"
--
Posted via http://www.ruby-forum.com/\.

I think that this is now well outside the scope of the original topic, so I will briefly say that implementing a semantically appropriate #eql? and #hash on your class is the way to make them behave as expected when used as hash keys and with methods like Array#&, honoring the principle of least surprise. It is as normal (and useful) as implementing #== for simple comparisons.

I hope that other Rubyists that may stumble upon this thread will take Robert's FUD with a grain of salt and will feel free to determine the usefulness and any potential dangers of implementing #eql? and #hash -- along with other Ruby idioms like #each (for Enumerable) and #<=> (for Comparable) -- on their own. An ounce of critical thinking is better than a pound of dogma.

···

--
Rein Henrichs
http://puppetlabs.com
http://reinh.com

If all objects you are dealing with implement #eql? and #hash in a way to be suitable for that comparison then using #eql? is the most straightforward approach.

If they do not and you want to do the comparison based on other criteria then you need to pick a different solution. For example:

in_both = obj_from_db.select do |u1|
   obj_from_xml.any? do |u2|
     u1.email == u2.email && u1.age == u2.age
   end
end

The larger set should be used for the outer iteration. If both sets are really large then you probably rather want to use a different strategy by speeding up access via a Hash. For that you need a specific Hash key (remember, we had assumed #eql? and #hash cannot be used). That key must implement #eql? and #hash; the easiest way to get such a Key class is to use Struct:

Key = Struct.new :email, :age

db_index = {}

obj_from_db.each do |u1|
   db_index[Key[u1.email, u1.age]] = u1
end

in_both = obj_from_xml.select do |u2|
   db_index[Key[u2.email, u2.age]]
end

Kind regards

  robert

···

On 05.06.2010 00:35, Anderson Leite wrote:

  Before

we can provide solutions we have to know what problem must be solved.

I have a list of objects that came from database and another list of
objects extracted from a xml. I need the elements who are in both lists.

Then...I thought to compare objects overriding the == method like Marcin
Wolski wrote. There is another solution ?

--
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/