rubyarrayssliceparallel-assignment

Parallel assignment in ruby working differently for two equivalent code fragments


The two code fragments below should print the same thing, but they don't.

ary = %W(1 2 5 6 B 8 5 4 6 5 6 9 7 A)
indx1 = 0...ary.index("B")
indx2 = (ary.index("A") + 1)..-1
ary[indx1], ary[indx2] = ary[indx2], ary[indx1]
puts ary.inspect

ary = %W(1 2 5 6 B 8 5 4 6 5 6 9 7 A)
ary[0...ary.index("B")], ary[(ary.index("A") + 1)..-1] = ary[(ary.index("A") + 1)..-1],  ary[0...ary.index("B")]
puts ary.inspect

The first prints:

["B", "8", "5", "4", "6", "5", "6", "9", "7", "A", nil, nil, nil, nil, "1", "2", "5", "6"]

and the second:

["B", "8", "5", "4", "6", "5", "6", "9", "7", "A", "1", "2", "5", "6"]

Shouldn't they print the same thing? They seem equivalent to me.

(Using Mac OSX 10.6.7 and ruby 1.9.2-p180)


Solution

  • They're different because in the first case you're pre-calculating the indices, while in the second you're calculating them dynamically, and the array changes between the first assignment and the second.

    Technically both RHS evaluations are made before the LHS assignments are made, but both LHS assignments can't be made simultaneously, so in this case you're effectively seeing that

    A, B = C, D
    

    is equivalent to

    A = C
    B = D
    

    So the first thing you do is...

    ary[0...ary.index("B")] = ary[(ary.index("A") + 1)..-1]
    

    in both cases. So ary is now

    ["B", "8", "5", "4", "6", "5", "6", "9", "7", "A"]
    

    Now originally you'd calculated indx1 and indx2 as 0...4 and 14..-1 respectively, but now if you recalculated the indx1 and indx2 values you'd have:

    indx1 = 0...ary.index("B")       #=> 0...0
    indx2 = (ary.index("A") + 1)..-1 #= 10..-1
    

    In other words,

    ary[indx2] = ary[indx1]
    

    is no longer equivalent to

    ary[(ary.index("A") + 1)..-1] = ary[0...ary.index("B")]
    

    That is,

    ary = ["B", "8", "5", "4", "6", "5", "6", "9", "7", "A"]
    ary[(ary.index("A") + 1)..-1] = ary[0...ary.index("B")]
    

    gives you

    ary #=> ["B", "8", "5", "4", "6", "5", "6", "9", "7", "A"]
    

    while

    ary = ["B", "8", "5", "4", "6", "5", "6", "9", "7", "A"]
    ary[indx2] = ary[indx1]
    

    gives you

    ary #=> ["B", "8", "5", "4", "6", "5", "6", "9", "7", "A", nil, nil, nil, nil, "1", "2", "5", "6"]