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
hashandeql?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]
