Why can a floating point number be used as an array index?

Why can a floating point number be used as an array index? Anybody
know of a good use case for this?

irb(main):020:0> [1, 2, 3][0.3]
1
irb(main):021:0> [1, 2, 3][0.9]
1

Just curious,
Jeff

Jeff D. wrote in post #989828:

Why can a floating point number be used as an array index? Anybody
know of a good use case for this?

irb(main):020:0> [1, 2, 3][0.3]
1
irb(main):021:0> [1, 2, 3][0.9]
1

Just curious,
Jeff

arr = [1,2,3,4…]
arr[(anyhting).to_i]

On Wed, Mar 30, 2011 at 03:35:21AM +0900, Jeff D. wrote:

Why can a floating point number be used as an array index? Anybody
know of a good use case for this?

irb(main):020:0> [1, 2, 3][0.3]
1
irb(main):021:0> [1, 2, 3][0.9]
1

I don’t know about why, exactly, or about use cases (though I suppose it
might just make things easier sometimes, without errors cropping up all
over the place if you’re working with floating point numbers a lot and
are too lazy to do integer conversions yourself). How seems, from a
little experimenting, to be self-evident to me:

$ irb
irb(main):001:0> [1,2,3][0.7]
=> 1
irb(main):002:0> [1,2,3][1.7]
=> 2
irb(main):003:0> [1,2,3][1.1]
=> 2
irb(main):004:0> [1,2,3][-0.1]
=> 1
irb(main):005:0> [1,2,3][-1.1]
=> 3

It looks like it just does integer truncation.

Keep in mind that arrays use the implicit integer conversion protocol,
#to_int, to make this conversion. You
can’t use any old object with #to_i:

[1, 2, 3][“1”]
TypeError: can’t convert String into Integer

Very few classes implement this protocol:

ObjectSpace.each_object { |a| p(a) if Module === a &&
a.instance_methods(false).include?(:to_int) }
Float
Integer
Numeric

I suspect the primary reason for allowing Arrays to convert floats to
appropriate indices is to simplify calculated array
indices in any case. Sam gives a good example, but there are even more
common, simple cases where
one might calculate array indices and not want to have to call #to_i
every time. Here’s one for determining
what item in a list was clicked:

chosen_item = items[ pixel_clicked.y / item_size ]

Michael E.
[email protected]
http://carboni.ca/

How about statistical bucketing. Imagine your indexes 1, 2, and 3 are Gb
of RSS or something.

irb
ruby-1.9.2-p0 > rss = [0.123, 1.223, 5.33, 2.294, 7.66, 5.6, 3.222,
3.88, 3.44, 1.78] # incoming rss values
=> [0.123, 1.223, 5.33, 2.294, 7.66, 5.6, 3.222, 3.88, 3.44, 1.78]

ruby-1.9.2-p0 > buckets = (0…rss.max).collect {|i| i} # bucket indexes
=> [0, 1, 2, 3, 4, 5, 6, 7]

ruby-1.9.2-p0 > rss.reduce(Hash.new(0)) {|h, v| h.tap{|h|
h[buckets[v]]+=1}} # statistics!
=> {0=>1, 1=>2, 5=>2, 2=>1, 7=>1, 3=>3}

I’m sure there are lots of (probably simpler) ways to do the same thing,
but it was a fun exercise nonetheless =]

Sam

On Tue, Mar 29, 2011 at 10:38 PM, [email protected] wrote:

It may do integer truncation, but we need to think of what an Array
actually is as far as the structure goes. What would one expect to get out
of [0,1,2][0.3] ? Should it be 30% of the first element? That doesn’t seem
right.

Well, alternatively you could get an error.

It may do integer truncation, but we need to think of what an Array
actually is as far as the structure goes. What would one expect to get
out
of [0,1,2][0.3] ? Should it be 30% of the first element? That doesn’t
seem
right. With arrays, you are either in one element or not. Integers make
more sense because they represent the location you’re in. Either the 0
spot, 1 spot, or 2 spot. Think of a row of lockers at school. You can’t
be
partially between each locker. It makes more sense to only use one
locker
or the next.

On Tue, Mar 29, 2011 at 3:59 PM, Xavier N. [email protected] wrote:

Seems a lot more reasonable, using floats could introduce errors:

almost one

123.6 - 123 + 0.4 # => 0.9999999999999943

but truncates to zero

(123.6 - 123 + 0.4).truncate # => 0

do the same math with integers

(1236 - 1230 + 4) / 10 # => 1

On Wed, Mar 30, 2011 at 06:50:24AM +0900, Josh C. wrote:

Seems a lot more reasonable, using floats could introduce errors:

Okay . . .

So. Now tell me why errors are superior, in actual, practical use
cases,
to integer truncation to return a meaningful result from the array.

I don’t really have a horse in this race at the moment. I just don’t
want to have someone change the default behavior of arrays for no reason
other than hypercorrect bikeshed painting.

On Tue, Mar 29, 2011 at 10:02 PM, Sniper A.
[email protected] wrote:

Jeff D. wrote in post #989828:

Why can a floating point number be used as an array index? Anybody
know of a good use case for this?

irb(main):020:0> [1, 2, 3][0.3]
1
irb(main):021:0> [1, 2, 3][0.9]
1

arr = [1,2,3,4…]
arr[(anyhting).to_i]

No, it’s using #to_int

irb(main):001:0> idx = Object.new
=> #Object:0x1091fd74
irb(main):002:0> %w{foo bar}[idx]
TypeError: can’t convert Object into Integer
from (irb):2:in []' from (irb):2 from /opt/bin/irb19:12:in
irb(main):003:0> class <<idx
irb(main):004:1> def to_i;p “to_i”;1;end
irb(main):005:1> end
=> nil
irb(main):006:0> %w{foo bar}[idx]
TypeError: can’t convert Object into Integer
from (irb):6:in []' from (irb):6 from /opt/bin/irb19:12:in
irb(main):007:0> class <<idx
irb(main):008:1> def to_int;p “to_int”;1;end
irb(main):009:1> end
=> nil
irb(main):010:0> %w{foo bar}[idx]
“to_int”
=> “bar”

#to_i is just a convenience conversion (e.g. String#to_i) while
#to_int is only used for things which are actually an integer. You
can see all implementators with “ri ‘#to_int’”.

Kind regards

robert

On Tue, Mar 29, 2011 at 10:38 PM, [email protected] wrote:

It may do integer truncation, but we need to think of what an Array
actually is as far as the structure goes. What would one expect to get out
of [0,1,2][0.3] ? Should it be 30% of the first element? That doesn’t seem
right. With arrays, you are either in one element or not. Integers make
more sense because they represent the location you’re in. Either the 0
spot, 1 spot, or 2 spot. Think of a row of lockers at school. You can’t be
partially between each locker. It makes more sense to only use one locker
or the next.

Yes, of course. That’s why Array#[] uses #to_int to determine whether
the index is integerish. The question to ask would be whether Float
should be “integerish” - Matz decided “yes” a long time ago and from
what I can recall there were not much complaints over time. From a
formal (or mathematical) point of view it’s rather the other way
round: an integer /is a/ float but a float /is not an/ integer. But
Ruby is a pragmatic language and so I believe they figured that it
would be more convenient to have Float#to_int than not. YMMV though
and you can easily overwrite Float#to_int to throw.

Kind regards

robert