Home > Software design >  Ruby on Rail bitwise operators (&) for the complex types arrays
Ruby on Rail bitwise operators (&) for the complex types arrays

Time:09-26

I am trying to use the bitwise operator (&) for the arrays in the Ruby on Rails

When I have the simple type arrays

one = ["one", "two", "three"]
two = ["one", "two", "three"]

And have this code

puts (one & two)

I get the output:

one two three

However, when I am having the complex types arrays

list1 = [Someitem.new("1", "item1"),Someitem.new("2", "item2"),Someitem.new("3", "item3"),Someitem.new("4", "item4")]
list2 = [Someitem.new("1", "item1"),Someitem.new("2", "item2"),Someitem.new("3", "item3"),Someitem.new("4", "item4")]

For the class where I do override the to_s method

class Someitem
    attr_accessor :item_id, :item_name
    def initialize(item_id, item_name)
      self.item_id = item_id;
      self.item_name= item_name;
    end
    
   def to_s
     item_name
   end  
end

And have this code

puts (list1 & list2)

I get nothing in the output

What can I do to get the output for the complex types arrays using the bitwise & operator to find common values within those two lists?

CodePudding user response:

The Ruby 3.0 docs for Array#& say:

[...] items are compared using eql?

The Ruby 2.7 docs for Array#& say:

It compares elements using their hash and eql? methods for efficiency

So in order to get this working for your class, you have to implement eql? and (for 2.7 compatibility) hash. There are many ways to do this, here's a simple approach:

class Someitem

  # ...

  def hash
    [item_id, item_name].hash
  end

  def eql?(other)
    return false unless other.is_a?(Someitem)

    [item_id, item_name] == [other.item_id, other.item_name]
  end
  alias == eql?  # <- this isn't need but convenient
end

The above implementation uses item_id and item_name to calculate the object's hash and to determine object equality:

Someitem.new("1", "item1").hash == Someitem.new("1", "item1").hash
#=> true

Someitem.new("1", "item1").eql? Someitem.new("1", "item1")
#=> true

[Someitem.new("1", "item1")] & [Someitem.new("1", "item1")]
#=> [#<Someitem:0x00007f8ff505d228 @item_id="1", @item_name="item1">]

CodePudding user response:

Firstly, while Integer#& is the bit-wise AND operator, Array#& is the set intersection operator (though I find the term "set" grating because an array is not a set).

You appear to be assuming that

Someitem.new("1", "item1") == Someitem.new("1", "item1") #=> true

However, the two instances are not the same object, so in fact:

Someitem.new("1", "item1") == Someitem.new("1", "item1") #=> false

Recalling that every object has a unique object_id, consider the following:

list1.map(&:object_id)
  #=> [1520, 1540, 1560, 1580]
list2.map(&:object_id)
  #=> [1600, 1620, 1640, 1660]

Since [1520, 1540, 1560, 1580] and [1600, 1620, 1640, 1660] have no common elements we see that their intersection is empty:

list1.map(&:object_id) & list2.map(&:object_id)
  #=> [1520, 1540, 1560, 1580] & [1600, 1620, 1640, 1660]
  #=> []

CodePudding user response:

When arrays are involved, & is actually the intersection, it's not the bitwise AND.

To return the common elements between your lists, the #hash method is called.

For your first example, calling "one".hash returns the same value all the time, which is actually a hit.

"one".hash # => 103347303317675750
"one".hash # => 103347303317675750

In the second example you work with different instances, which, of course don't match.

Someitem.new("1", "item1").hash # => a_value
Someitem.new("1", "item1").hash # => another_value

If you pass the same instance to both arrays, it will match.

 a = Someitem.new("1", "item1")
 a.hash # => a_value
 a.hash # => same_value_as_above

 list1 = [a]
 list2 = [a]
 list1 & list2 # => [a]
  • Related