6

Today I came across some strange behaviour of Array element assignment:

arr = ["a","b"]
arr2 = [1,2]
arr.unshift(arr2) #= [[1, 2], "a", "b"] 
arr.push(arr2) #=> ["a", "b", [1, 2]] 

This makes sense, however:

arr[0,0] = arr2 #=> [1, 2, "a", "b"] 

I know that in [0,0] the first zero is index and second is the number of elements to be effected in that array starting from index.

In my thoughts it should be the same as the unshift, but it's not.

Can any one explain the behavior?

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
Mani
  • 2,391
  • 5
  • 37
  • 81
  • 1
    Why do you think `[]` should work in the same way as `unshift`? They are different methods. – sawa Feb 11 '16 at 18:28
  • @sawa you're right , that was just a thing , but can you explain [0,0] behavior . It's quite different – Mani Feb 11 '16 at 18:29
  • I believe the doc for [Array#[\]=](http://ruby-doc.org/core-2.2.0/Array.html#method-i-5B-5D-3D) is quite clear: "`ary[start, length] = obj`...replaces a subarray [with the elements of `obj`] from the `start` index for `length` elements". Here you might find clearer to write `arr.insert(0,arr2)`. (If you want to insert the `arr2` at the end of `arr`, you could write `arr[arr.size,0] = arr2` or `arr.insert(arr.size, *arr2)`). Note the parallel with [str\[fixnum, fixnum\] = new_str](http://ruby-doc.org/core-2.2.0/String.html#method-i-5B-5D-3D). – Cary Swoveland Feb 11 '16 at 22:03

1 Answers1

2

If we dive into the ruby source code, we'll find a function named rb_ary_splice called when array assignment happens with three arguments (i.e. index, length, and new value):

static VALUE
rb_ary_aset(int argc, VALUE *argv, VALUE ary)
{
    long offset, beg, len;

    if (argc == 3) {
        rb_ary_modify_check(ary);
        beg = NUM2LONG(argv[0]);
        len = NUM2LONG(argv[1]);
        rb_ary_splice(ary, beg, len, argv[2]);
        return argv[2];
    }
[...]

And if we follow along in rb_ary_splice we'll happen upon where the magic happens:

static void
rb_ary_splice(VALUE ary, long beg, long len, VALUE rpl)
{
    long rlen;
    long olen;

    if (len < 0) rb_raise(rb_eIndexError, "negative length (%ld)", len);
    olen = RARRAY_LEN(ary);

    [...]

        if (len != rlen) {
            RARRAY_PTR_USE(ary, ptr,
                   MEMMOVE(ptr + beg + rlen, ptr + beg + len,
                       VALUE, olen - (beg + len)));
            ARY_SET_LEN(ary, alen);
        }
        if (rlen > 0) {
            MEMMOVE(RARRAY_PTR(ary) + beg, RARRAY_CONST_PTR(rpl), VALUE, rlen);
        }
    }
    RB_GC_GUARD(rpl);
}

First it makes room in the array for the new elements and updates the length:

RARRAY_PTR_USE(ary, ptr,
    MEMMOVE(ptr + beg + rlen, ptr + beg + len,
        VALUE, olen - (beg + len)));
ARY_SET_LEN(ary, alen);

Then through the magic of C pointers, it inserts the new element(s):

MEMMOVE(RARRAY_PTR(ary) + beg, RARRAY_CONST_PTR(rpl), VALUE, rlen);
Skrat
  • 577
  • 2
  • 15