Distinguish between decimal index and integer inside an array in Ruby?
Using a block together with eql?
is one of the ways:
[1,2.0,2,3].index {|e| e.eql? 2.0}
#=> 1
[1,2.0,2,3].index {|e| e.eql? 2}
#=> 2
In contrast ==
, eql?
returns true
only if the receiver and argument are of the same type and have equal values. Therefore it 2 == 2.0
returns true
and 2.eql? 2.0
returns false
.
source to share
Array index and equality
Are you not getting the results you expected because Array # index uses the more general BasicObject # == instead of Object # eql? for comparing values ββthat do not take an argument / value type. In duck typing language, this is usually what you want.
Consider the following example, which uses Float==
to compare with Fixnum :
2 == 2.0
#=> true
Note that Ruby assumes that two numeric values ββare equal despite being of different types. This is a documented behavior . Since the unblocked form of index Array # returns the first index where the argument ==
matches the indexed value [1,2.0,2,3].index(2.0)
and [1,2.0,2,3].index(2)
will return the same index.
Use Block Form of Array # index
All Ruby methods accept an optional block, and some base classes behave differently when Kernel # block_given? is true. The documentation for the Array # index states:
If a block is given ... returns the index of the first object for which the block returns true. Returns nil if no match is found.
The canonical way to differentiate between two different value types would use the block form of the Array # index to test for equality of a C # eql object? not C # ==. For example:
array = [1,2.0,2,3]
array.index { |i| i.eql? 2 }
array.index { |i| i.eql? 2.0 }
This returns the values ββyou expect, at the cost of a little extra typing. This is indeed the preferred solution to your problem.
Monkey-Patch array class
Base class pain relief like Array is usually a bad idea and trade; but you can make Array # index behave the way you want by reopening the Array class and changing the behavior of the Array # index to check for both type and value. One way to do this is with Module # alias_method and using Array # old_index block syntax to check for NumeriC # eql? whenever you call Array # index with a numeric argument.
Consider the following:
class Array
alias_method :old_index, :index
def index value
old_index { |i| i.eql? value }
end
end
[1,2.0,2,3].index 2.0
#=> 1
[1,2.0,2,3].index 2
#=> 2
This works as you seem to expect, and you can still use Array # old_index anytime you want to do agnostic authentication. However, use with caution, as other modules or gems may not meet expectations if you change the normal behavior of the Array class.
It's nice to know you can do this, but juggling chainsaws is a risky activity. Your mileage may vary.
source to share