3

Since Ruby does 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
0

2 Answers 2

5

Using a block together with eql? is one way:

[1,2.0,2,3].index {|e| e.eql? 2.0}
#=> 1
[1,2.0,2,3].index {|e| e.eql? 2}
#=> 2

Unlike ==, eql? returns true only if the receiver and the argument have the same type and equal values. So 2 == 2.0 returns true while 2.eql? 2.0 returns false.

Sign up to request clarification or add additional context in comments.

Comments

1

Array#index and Equality

You aren't getting the results you expect because Array#index uses the more-generic BasicObject#== instead of Object#eql? to compare values, which doesn't take the type of the argument/value into account. In a duck-typing language, this is usually what you want.

Consider the following example, which uses == to compare a Float to a Fixnum:

2 == 2.0
#=> true

Note that Ruby considers the two numeric values to be equal, despite being of different types. This is documented behavior. Since the non-block form of Array#index returns the first index where the argument is == to the indexed value, [1,2.0,2,3].index(2.0) and [1,2.0,2,3].index(2) will both return the same index.

Use Block Form of Array#index

All Ruby methods accept an optional block, and some core classes behave differently when Kernel#block_given? is true. The documentation for Array#index says:

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 types of values would use the block-form of Array#index to check for object equality with #eql? rather than with #==. 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'd expect, at the cost of a little extra typing. This is really the preferred solution to your problem.

Monkey-Patch the Array Class

Monkey-patching a core class like Array is generally a Bad Idea™, but you can force Array#index to behave the way you want by re-opening the Array class and modifying the behavior of Array#index to check for both type and value equality. One way to do this is with the help of Module#alias_method, and by using the block syntax of Array#old_index to check 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 the way you seem to expect, and you can still use Array#old_index anytime you want the original type-agnostic equality check. However, use with care, as other modules or gems might not behave as expected once you've changed the normal behavior of the Array class.

It's nice to know you can do this, but juggling with chainsaws is an inherently risky activity. Your mileage may vary.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.