zychyz
zychyz

Reputation: 53

Why is my code to swap two elements of a list going wrong?

Here is my code:

a = [1, 2, 3, 4, 5]
a[0], a[a[0]] = a[a[0]], a[0]
print(a)

I'm trying to swap a[0] with a[a[0]] (i.e. a[1] in this case), so the result I expect is:

[2, 1, 3, 4, 5]

The result I get is [2, 2, 1, 4, 5], which is not what I want.

if I simplify a[0], a[a[0]] = a[a[0]], a[0] to a[0], a[1] = a[1], a[0], it works.

How can I make this swap inside a list work like a, b = b, a does?

Upvotes: 5

Views: 123

Answers (2)

schesis
schesis

Reputation: 59118

That assignment's doing quite a lot. Let's break everything down …

a = [1, 2, 3, 4, 5]

Ok, that's the easy bit. Next:

a[0], a[a[0]] = a[a[0]], a[0]

The first thing that happens in any assignment is that the right hand side is evaluated, so:

a[a[0]], a[0] reduces to a[1], a[0], which evaluates to (2, 1).

Then, each assignment target in turn gets one of those items from the right hand side assigned to it:

a[0] = 2   # 2 == first item in the (already evaluated) right hand side

Now that's done, a looks like this:

[2, 2, 3, 4, 5]

Now we'll do the second assignment:

a[a[0]] = 1   # 1 == second item in the (already evaluated) right hand side

But wait! a[0] is now 2, so this reduces to

a[2] = 1

And, lo and behold, if we look at a again, it's ended up as:

[2, 2, 1, 4, 5]

What you've discovered is that although Python claims to be able to swap two values simultaneously with e.g. a, b = b, a, that isn't really true. It almost always works in practice, but if one of the values is part of the description of the other one – in this case, a[0] is part of the description of a[a[0]] – the implementation details can trip you up.

The way to fix this is to store the initial value of a[0] before you start reassigning things:

a = [1, 2, 3, 4, 5]
tmp = a[0]
a[0], a[tmp] = a[tmp], a[0]

After which, a looks the way you'd expect:

[2, 1, 3, 4, 5]

Upvotes: 2

Rhys Pang
Rhys Pang

Reputation: 223

python system lib dis module may help. The dis module supports the analysis of CPython bytecode by disassembling it. you can disassembling it to see how the swap works internally.

In [1]: import dis
In [2]: def func():
    ...:     a = [1, 2, 3, 4, 5]
    ...:     a[0], a[a[0]] = a[a[0]], a[0]
    ...:     print a

In [3]: func()
[2, 2, 1, 4, 5]

In [4]: dis.dis(func)
  2           0 LOAD_CONST               1 (1)
              3 LOAD_CONST               2 (2)
              6 LOAD_CONST               3 (3)
              9 LOAD_CONST               4 (4)
             12 LOAD_CONST               5 (5)
             15 BUILD_LIST               5
             18 STORE_FAST               0 (a)   # make list: a = [1, 2, 3, 4, 5]

  3          21 LOAD_FAST                0 (a)   # stack: a
             24 LOAD_FAST                0 (a)   # stack: a|a
             27 LOAD_CONST               6 (0)   # stack: a|a|0
             30 BINARY_SUBSCR                    # stack: a|1
             31 BINARY_SUBSCR                    # stack: 2
             32 LOAD_FAST                0 (a)   # stack: 2|a
             35 LOAD_CONST               6 (0)   # stack: 2|a|0
             38 BINARY_SUBSCR                    # stack: 2|1
             39 ROT_TWO                          # stack: 1|2
             40 LOAD_FAST                0 (a)   # stack: 1|2|a
             43 LOAD_CONST               6 (0)   # stack: 1|2|a|0
             46 STORE_SUBSCR                     # stack: 1|          a: a[0] = 2
             47 LOAD_FAST                0 (a)   # stack: 1|a
             50 LOAD_FAST                0 (a)   # stack: 1|a|a
             53 LOAD_CONST               6 (0)   # stack: 1|a|a|0
             56 BINARY_SUBSCR                    # stack: 1|a|2
             57 STORE_SUBSCR                     # stack:             a: a[2] = 1

  4          58 LOAD_FAST                0 (a)
             61 PRINT_ITEM
             62 PRINT_NEWLINE
             63 LOAD_CONST               0 (None)
             66 RETURN_VALUE

https://docs.python.org/3/library/dis.html

Upvotes: 0

Related Questions