Distinguish between decimal index and integer inside an array in Ruby?

Since Ruby does the type conversion, how do I get the index correctly?

I would like this to return 1

[1,2.0,2,3].index(2.0)
#=> 1

      

I would like this to return 2

[1,2.0,2,3].index(2)
#=> 1

      

+3


source to share


2 answers


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

.

+5


source


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.

+1


source







All Articles