Comparing objects in ruby
class Aaa
attr_accessor :a, :b
def ==(other)
return self.a == other.a && self.b == other.b
end
end
x = Aaa.new
x.a,x.b = 1,2
y = Aaa.new
y.a,y.b = 1,2
y = Aaa.new
y.a,y.b = 1,2
z = Aaa.new
z.a,z.b = 1,3
x == y # => true
x == z # => false
Comparing object equivalence in Ruby
In most cases, ==
and eql?
have the same result. In some cases, eql?
is more strict than ==
:
42.0 == 42 # => true
42.0.eql?(42) # => false
Because of this, if you define ==
you probably want to define eql?
also (or vice versa).
A choice was made that the Hash
class would use eql?
to differentiate between different keys, not ==
. It could have been ==
, mind you, but eql?
was cleaner.
To avoid doing expensive calls to eql?
all the time, a hash value is calculated with the requirement that two object that are eql?
must have the same hash value. That hash value is stored, which makes future lookups very easy: if the hash code does not match, then the values are not eql?
...
For that reason, you must define hash
in a sensible way if you define eql?
.
Note that calculating the hash value is almost always more expensive than doing a comparison with ==
or eql?
. Once the hash is calculated, though, checking that the hashes matches is very quick.
Because hashes normally involve very many comparisons, the relatively expensive hash calculation is done once for each key, and then once for each lookup. Imagine a hash with 10 entries. Building it will involve 10 calls to hash
, before the first lookup is even done. The first lookup will be relatively quick though: one call to hash
, followed by very efficient comparison of hash codes (it's actually faster than this, as they are "indexed"). If there is a match, one must still do a call to eql?
to insure it's a real match. Indeed, two objects that are not eql?
could have the same hash. The only guarantee is that two objects that are eql?
must have the same hash, but two different objects could have the same too.
If you wanted to do the same using an Array
instead, you might need 10 call to eql?
for each lookup.
For what it's worth, I don't think the Ruby primer you link to is as clear as it could be. It neglects the fact that calculating the hash
can be expensive, so that it's done only when it makes sense, i.e. when it is a good assumption that each element will be compared many times. Moreover, it's a shame that the example of a custom eql?
it gives uses ==
to compare the instance variables. Ideally, it would use eql?
for consistency, in the same way that arrays are ==
if its elements are ==
and arrays are eql?
if its elements are eql?
. Finally, it really should mention Struct
which defines decent ==
, hash
and eql?
for you.
How to compare two objects in ruby
Probably something like this:
class User < ActiveRecord::Base
# ...
# untested, but the logic seems sound.
def equals?(user)
User.attributes.keys.each do |k|
return false unless self[k] == user[k]
end
true
end
end
You could then call current_user.equals?(existing_user)
.
Guaranteed way to compare objects by identity in Ruby
You could grab an unbound version of Object#object_id
, bind it to the object in question, and see what it says. Given your F
class with one addition:
class F
# ...
def inspect; 'pancakes!' end # Just so we can tell what we have later.
end
Then:
>> f = F.new
>> f.object_id
=> pancakes!
>> unbound_object_id = Object.instance_method(:object_id)
>> unbound_object_id.bind(f).call
=> 2153000340
>> ObjectSpace._id2ref(2153000340).inspect
=> "pancakes!"
Of course, if someone opens up Object and replaces object_id
then you're out of luck but this will be the least of your problems if someone does that. If you can grab your unbound_object_id
UnboundMethod before anything else is loaded, then it won't matter if someone changes Object#object_id
as your unbound_object_id
will still be the original correct one.
So this round-about hack gives you a reliable object_id
for any object (subject to the caveats above). Now you can grab and compare the object ids to get your reliable comparison.
Is there any way to specify how to compare of array of objects for .difference function in Ruby 2.6.0?
If you don't want to add eql?
to the class as described by Aleksei Matiushkin (e.g. if you want to use multiple criteria for different things), there's no way to reuse #difference
. Doing what you were doing is pretty much what you need to do, though with Array#include?
is O(N^2), so I like sticking Set
in there:
Set.new(subset.map(&:val)).then { |s| all.reject { |x| s === x.val } }
# => [#<Num:0x00007febd32330e0 @val=3>]
or, as a new method:
module ArrayWithDifferenceBy
refine Array do
def difference_by(other)
other_set = Set.new(other.map { |x| yield x })
self.reject { |x| other_set.include?(yield x) }
end
end
end
module TestThis
using ArrayWithDifferenceBy
all = [Num.new(1), Num.new(2), Num.new(3)]
subset = [Num.new(1), Num.new(2)]
all.difference_by(subset, &:val)
end
# => [#<Num:0x00007febd32330e0 @val=3>]
Related Topics
Recommended Two-Way Encryption Gems for Ruby
Is There a Gem That Normalizes and Format Us Phone Numbers in Ruby
Detecting Overlapping Ranges in Ruby
Rails Render of Partial and Layout in Controller
Rails 5.0.0 When Installing "Nio4R":Failed to Build Gem Native Extension
What Do 'Def +@' and 'Def -@' Mean
Get Response Headers from Curb
Where to Put a Before_Filter Shared Between Multiple Controllers
Converting String "2½" (Two and a Half) into 2.5
Paperclip File Not Found Error
How to Delete a Range of Values from an Array
How to Make Xcode Use the Correct Version of Ruby When Running a Script
Change String in a Date Format to Another Format
Serialize Array with Strong_Parameters
Implementing a Synchronization Barrier in Ruby
Ruby - Test Whether Database Connection Is Possible
Ransack, Postgres - Sort on Column from Associated Table with Distinct: True
What Are the Differences Between Lazy, Greedy and Possessive Quantifiers