4

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?

Community
  • 1
  • 1
Mladen Jablanović
  • 43,461
  • 10
  • 90
  • 113
  • Parallel assignment is useful, but for this sort of task where you're looking up the locations of the variables in the array before proceeding it seems like a Hash would be a more usable structure because of its direct lookup. If you're doing your assignment repeatedly, like in an inner loop, it will be costly and a hash will outrun it. – the Tin Man Nov 15 '10 at 15:07
  • Agreed, the example here was hypothetical, taken partly from the other question. – Mladen Jablanović Nov 15 '10 at 15:55
  • And now I've created a question based on your question! – Andrew Grimm Dec 02 '10 at 23:43
  • You should totally drop what you're doing and switch to functional programming! – Andrew Grimm Dec 02 '10 at 23:43
  • @Greg: I reverted the title, I really don't think the question form is right. When naming my questions, I follow this simple principle: "How would I phrase the Google query I would like to match my SO question". – Mladen Jablanović Dec 10 '10 at 09:45

3 Answers3

3

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'])
1

Just as an example, compare the machinations used to search the array, find the correct indexes then swap the values, with what you could do using a Hash:

h = { "cat" => "feline", "dog" => "canine", "cow" => "bovine" }

h['dog'], h['cat'] = h.values_at('cat', 'dog')

h #=> {"cat"=>"canine", "dog"=>"feline", "cow"=>"bovine"}

Now, if Ruby had an assignable values_at= Hash method it could be even cleaner:

h.values_at('dog', 'cat') = h.values_at('cat', 'dog')

but, alas, we don't. Hash slicing is a very powerful tool in Perl and something I miss about Ruby.

And, yes, I know I can add my own assignable values_at=.

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
1

M Rajesh is correct, but he actually had to think in order to work it out. I'm too lazy for that!

Here's a printf-debugging way of showing what happened.

deck = ['A', 'B', 'C']
#=> ["A", "B", "C"]
deck[deck.index("A").tap {|index| 
  STDERR.puts "Result of indexing for #{"A".inspect} is #{index.inspect}"
  }], 
deck[deck.index("B").tap {|index| 
  STDERR.puts "Result of indexing for #{"B".inspect} is #{index.inspect}"
  }] = 
deck[deck.index("B")], deck[deck.index("A")]
# Result of indexing for "A" is 0
# Result of indexing for "B" is 0
#=> ["B", "A"]
deck
#=> ["A", "B", "C"]
Andrew Grimm
  • 78,473
  • 57
  • 200
  • 338