Array slicing in Ruby: explanation for illogical behaviour (taken from Rubykoans.com)

后端 未结 10 2216
没有蜡笔的小新
没有蜡笔的小新 2020-11-22 10:39

I was going through the exercises in Ruby Koans and I was struck by the following Ruby quirk that I found really unexplainable:

array = [:peanut, :butter, :a         


        
相关标签:
10条回答
  • 2020-11-22 11:09

    Slicing and indexing are two different operations, and inferring the behaviour of one from the other is where your problem lies.

    The first argument in slice identifies not the element but the places between elements, defining spans (and not elements themselves):

      :peanut   :butter   :and   :jelly
    0         1         2      3        4
    

    4 is still within the array, just barely; if you request 0 elements, you get the empty end of the array. But there is no index 5, so you can't slice from there.

    When you do index (like array[4]), you are pointing at elements themselves, so the indices only go from 0 to 3.

    0 讨论(0)
  • 2020-11-22 11:09

    This does make sense

    You need to be able to assign to those slices, so they are defined in such a way that the beginning and the end of the string have working zero-length expressions.

    array[4, 0] = :sandwich
    array[0, 0] = :crunchy
    => [:crunchy, :peanut, :butter, :and, :jelly, :sandwich]
    
    0 讨论(0)
  • 2020-11-22 11:12

    Consider the following array:

    >> array=["a","b","c"]
    => ["a", "b", "c"]
    

    You can insert an item to the begining (head) of the array by assigning it to a[0,0]. To put the element between "a" and "b", use a[1,0]. Basically, in the notation a[i,n], i represents an index and n a number of elements. When n=0, it defines a position between the elements of the array.

    Now if you think about the end of the array, how can you append an item to its end using the notation described above? Simple, assign the value to a[3,0]. This is the tail of the array.

    So, if you try to access the element at a[3,0], you will get []. In this case you are still in the range of the array. But if you try to access a[4,0], you'll get nil as return value, since you're not within the range of the array anymore.

    Read more about it at http://mybrainstormings.wordpress.com/2012/09/10/arrays-in-ruby/ .

    0 讨论(0)
  • 2020-11-22 11:13

    tl;dr: in the source code in array.c, different functions are called depending on whether you pass 1 or 2 arguments in to Array#slice resulting in the unexpected return values.

    (First off, I'd like to point out that I don't code in C, but have been using Ruby for years. So if you're not familiar with C, but you take a few minutes to familiarize yourself with the basics of functions and variables it's really not that hard to follow the Ruby source code, as demonstrated below. This answer is based on Ruby v2.3, but is more or less the same back to v1.9.)

    Scenario #1

    array.length == 4; array.slice(4) #=> nil

    If you look at the source code for Array#slice (rb_ary_aref), you see that when only one argument is passed in (lines 1277-1289), rb_ary_entry is called, passing in the index value (which can be positive or negative).

    rb_ary_entry then calculates the position of the requested element from the beginning of the array (in other words, if a negative index is passed in, it computes the positive equivalent) and then calls rb_ary_elt to get the requested element.

    As expected, rb_ary_elt returns nil when the length of the array len is less than or equal to the index (here called offset).

    1189:  if (offset < 0 || len <= offset) {
    1190:    return Qnil;
    1191:  } 
    

    Scenario #2

    array.length == 4; array.slice(4, 0) #=> []

    However when 2 arguments are passed in (i.e. the starting index beg, and length of the slice len), rb_ary_subseq is called.

    In rb_ary_subseq, if the starting index beg is greater than the array length alen, nil is returned:

    1208:  long alen = RARRAY_LEN(ary);
    1209:
    1210:  if (beg > alen) return Qnil;
    

    Otherwise the length of the resulting slice len is calculated, and if it's determined to be zero, an empty array is returned:

    1213:  if (alen < len || alen < beg + len) {
    1214:  len = alen - beg;
    1215:  }
    1216:  klass = rb_obj_class(ary);
    1217:  if (len == 0) return ary_new(klass, 0);
    

    So since the starting index of 4 is not greater than array.length, an empty array is returned instead of the nil value that one might expect.

    Question answered?

    If the actual question here isn't "What code causes this to happen?", but rather, "Why did Matz do it this way?", well you'll just have to buy him a cup of coffee at the next RubyConf and ask him.

    0 讨论(0)
提交回复
热议问题