开发者

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?


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


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=.


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"]
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜