rubyarraysparallel-assignment

Swapping array elements using parallel assignment


Intrigued by this question, I have played a bit with parallel assignment with arrays and method calls. So here's an paradigmatic example, trying to swap two members in an array, by their value:

deck = ['A', 'B', 'C']
#=> ["A", "B", "C"]
deck[deck.index("A")], deck[deck.index("B")] = deck[deck.index("B")], deck[deck.index("A")]
#=> ["B", "A"]
deck
#=> ["A", "B", "C"]

The array hasn't changed. But if we change the order of arguments, it works:

deck[deck.index("B")], deck[deck.index("A")] = deck[deck.index("A")], deck[deck.index("B")]
#=> ["A", "B"]
deck
#=> ["B", "A", "C"]

I guess it has to do with the order of calling the index methods within the assignment, but not see it clearly. Can someone please explain the order of things underneath, and why the first example doesn't swap the member, and second does?


Solution

  • It is expected. It follows from how ruby evaluates expressions.

    deck[deck.index("A")], deck[deck.index("B")] = deck[deck.index("B")], deck[deck.index("A")]
    

    Implies

    deck[deck.index("A")], deck[deck.index("B")] = 'B', 'A'
    

    Note: strings 'A' and 'B' here are for illustration only. Ruby doesn't create new string objects here. Which essentially is:

    deck[deck.index("A")] = 'B' -> deck[0] = 'B' (deck = ['B', 'B', 'C'])
    deck[deck.index("B")] = 'A' -> deck[0] = 'A' (deck = ['A', 'B', 'C'])
    

    Array#index returns when it finds the first match.

    Now,

    deck[deck.index("B")], deck[deck.index("A")] = deck[deck.index("A")], deck[deck.index("B")]
    -> deck[deck.index("B")], deck[deck.index("A")] = 'A', 'B'
    -> deck[deck.index("B")] = 'A' -> deck[1] = 'A' (deck = ['A', 'A', 'C'])
    -> deck[deck.index("A")] = 'B' -> deck[0] = 'B' (deck = ['B', 'A', 'C'])