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