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 ?
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.
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
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 ?
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
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:
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....
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 ?
"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.
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.
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.
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
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:
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
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.
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.
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 ?