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

匿名 (未验证) 提交于 2019-12-03 08:59:04

问题:

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 

回答1:

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.



回答2:

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.



标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!