Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a Ruby method for determining if all instance variables of two instances of the same class are equal?

Tags:

ruby

Is there a Ruby method for comparing two objects based on whether all of their instance variables are equal? The method would behave like this code.

class Coordinates
  attr_reader :x, :y
  def initialize(x, y)
    @x = x
    @y = y
  end
end

coordinates1 = Coordinates.new(0, 0)
coordinates2 = Coordinates.new(0, 0)
coordinates3 = Coordinates.new(1, 0)

compare(coordinates1, coordinates1) # => true
compare(coordinates1, coordinates2) # => true
compare(coordinates1, coordinates3) # => false

Does this method or something similar exist?

like image 592
fossegrim Avatar asked Jan 27 '26 01:01

fossegrim


2 Answers

There is no built-in method for this, but you could quite easily write one. However, I think you're asking an XY question.

Here is what I think the question is supposed to say:

How should I define a method to check that two Coordinates instances are equal?

And here's my answer:

Define a custom == method:

class Coordinates
  attr_reader :x, :y
  def initialize(x, y)
    @x = x
    @y = y
  end

  def ==(other)
    return super unless other.is_a?(Coordinates)

    x == other.x && y == other.y
  end
end

...But in the spirit of StackOverflow, here's some meta-programming to check whether all instance variables have the same name and value:

# returns true if all objects have the same instance variable names and values
def compare(*objects)
  objects.map do |object|
    object.instance_variables.map do |var_name|
      [var_name, object.instance_variable_get(var_name)]
    end
  end.uniq.count == 1
end
like image 73
Tom Lord Avatar answered Jan 30 '26 07:01

Tom Lord


Case 1

class A
  def initialize(x,y)
    @x = x
    @y = y
  end
  def m
    @x = 5
    @y = 6
  end
end

a1 = A.new(1,2)
   #=> #<A:0x00005d22a3878048 @x=1, @y=2> 
a1.m
a1 #=> #<A:0x00005d22a3878048 @x=5, @y=6> 

a2 = A.new(3,4)
   #=> #<A:0x00005d22a38b5330 @x=3, @y=4> 
a2.m
a2 #=> #<A:0x00005d22a38b5330 @x=5, @y=6> 

Then,

a1.instance_variables.all? { |e|
  a1.instance_variable_get(e) == a2.instance_variable_get(e) }
  #=> true

tells us that the values of @x and the values of @y are the same for both instances.

Case 2

Now let's change the code so that another instance variable is added conditionally.

class A
  def initialize(x,y)
    @x = x
    @y = y
  end
  def m
    @z = 3 if @x == 3
    @x = 5
    @y = 6
  end
end

a1 = A.new(1,2)
   #=> #<A:0x000057d1fd563c78 @x=1, @y=2> 
a1.m
a1 #=> #<A:0x000057d1fd27f200 @x=5, @y=6> 

a2 = A.new(3,4)
   #=> #<A:0x000057d1fd57cb38 @x=3, @y=4>
a2.m
a2 #=> #<A:0x000057d1fd2f9e10 @x=5, @y=6, @z=3> 

At this point are all instance variables of one of these instances equal to the corresponding instance variable of the other instance? No, because a2 has an additional instance variable, @z. Therefore,

a1.instance_variables.all? { |e|
  a1.instance_variable_get(e) == a2.instance_variable_get(e) }
  #=> true

gives the wrong answer, for obvious reasons. Perhaps we could test as follows:

a1.instance_variables.all? { |e|
  a1.instance_variable_get(e) == a2.instance_variable_get(e) } &&
a2.instance_variables.all? { |e|
  a1.instance_variable_get(e) == a2.instance_variable_get(e) } 
 #=> true && false => false

This has a gotcha, however, if @z equals nil.

Case 3

class A
  def initialize(x,y)
    @x = x
    @y = y
  end
  def m
    @z = nil if @x == 3
    @x = 5
    @y = 6
  end
end

a1 = A.new(1,2)
   #=> #<A:0x000057d1fd2d18e8 @x=1, @y=2> 
a1.m
a1 #=> #<A:0x000057d1fd2d18e8 @x=5, @y=6> 

a2 = A.new(3,4)
  #=> #<A:0x000057d1fd46b460 @x=3, @y=4> 
a2.m
a2
  #=> #<A:0x000057d1fd46b460 @x=5, @y=6, @z=nil> 

a1.instance_variables.all? { |e|
  a1.instance_variable_get(e) == a2.instance_variable_get(e) } &&
a2.instance_variables.all? { |e|
  a1.instance_variable_get(e) == a2.instance_variable_get(e) }
  #=> true && true => true

We obtain this incorrect result because:

class A
end

A.new.instance_variable_get(:@z)
  #=> nil

We therefore must confirm that if one instance has an instance variable named e, so does the other instance, and that each pair of instance variables with the same name are equal. One way to do that is as follows:

(a1.instance_variables.sort == a2.instance_variables.sort) &&
a1.instance_variables.all? { |e|
  a1.instance_variable_get(e) == a2.instance_variable_get(e) }    
  #=> false && true => false

See Enumerable#all?, Object#instance_variables and Object#instance_variable_get.

like image 33
Cary Swoveland Avatar answered Jan 30 '26 06:01

Cary Swoveland