views:

54

answers:

3

I'm learning Ruby, and have just gotten into some stuff about arrays and ranges. I've run into something about slices that, while it makes sense at first glance, confuses me a bit when i look deeper into it.

irb says that (2..-1).to_a is an empty array. Meaning no values in the range, right?
But if i use that same range in [:a, :b, :c, :d, :e][2..-1], i get back [:c, :d, :e] rather than an empty array.

Now, i'm aware that -1 represents the last element of the array, so it kinda makes sense that what got picked, did. But if the range itself would be empty, how is it selecting anything?

+2  A: 

In the slicing operation it's not seeing the range as a Range per se, it's just looking at the values of the endpoints, in this case 2 and -1. Since -1 has a special meaning (i.e last item in the list) it just returns everything from item at index 2 to the end of the list. Don't think of it as a Range here, just think of it as a convenient way of passing in two numbers (expressing two end points).

banister
+3  A: 

This is a fascinating question. The answer is that it's not the individual elements of the range that are inspected when slicing the array, but the first and last elements. Specifically:

>> (2..-1).to_a
=> []
>> (2..-1).first
=> 2
>> (2..-1).last
=> -1

Thus the example works, since it slices the array from the [2] element to the [-1] element.

If you want a consistent way to think about this, consider that (2..-1).to_a outputs the integers found between 2 and -1 (of which there are none), but that [2..-1] means from the 2 index to the -1 index.

(Source: array.c and range.c in the Ruby source.)

And, the complicated bonus part: to get the meaning you were thinking about, you could use

>> [:a, :b, :c, :d, :e].values_at *(2..-1).to_a
=> []
Peter
For future reference, what's the star in `*(2..-1).to_a` for?
cHao
that would be the 'splat' operator.
Peter
More precisely: `foo([1,2,3])` calls `foo` with one (list) argument. `foo(*[1,2,3])` calls it with three: it is equivalent to `foo(1,2,3)`; the list is deconstructed and its elements splattered over the function call.
Amadan
A: 

The Array#[] method does not use the range as a range (i.e. it does not call include? on it or anything like that), it just takes the two numbers out of it and checks whether it's exclusive.

You can imagine it to look somewhat like this (though the real [] is implemented in C, not in ruby, and of course also handles arguments other than ranges):

def [](range)
  start = range.begin
  length = range.end - start
  length -= 1 if range.exclude_end?
  slice(start, length)
end
sepp2k