Company logo
  • Jobs
  • Bootcamp
  • About Us
  • For professionals
    • Home
    • Jobs
    • Courses
    • Questions
    • Teachers
    • Bootcamp
  • For business
    • Home
    • Our process
    • Plans
    • Assessments
    • Payroll
    • Blog
    • Sales
    • Calculator

0

105
Views
Simultaneous assignment indexing different list elements in Python
>>arr = [4, 2, 1, 3]
>>arr[0], arr[arr[0]-1] = arr[arr[0]-1], arr[0]
>>arr

Result I expect >>[3, 2, 1, 4]

Result I get >>[3, 2, 4, 3]

Basically I'm trying to swap the #4 and #3 (In my actual problem, the index wont be 0, but rather an iterator "i" . So I cant just do arr[0], arr[3] = arr[3], arr[0]) I thought I understood simultaneous assignment fairly well. Apparently I was mistaken. I don't understand why arr[arr[0]-1] on the left side of the assignment is evaluating to arr[2] instead of arr[3]. If the assignments happen simultaneously (evaluated from the right),

arr[0] (within the index of the 2nd element on the left)should still be "4"

arr[0] -1 (the index of the 2nd element on the left) should thus be "3"

10 months ago · Santiago Trujillo
3 answers
Answer question

0

Because the target list does not get evaluated simultaneously. Here is the relevant section of the docs:

The object must be an iterable with the same number of items as there are targets in the target list, and the items are assigned, from left to right, to the corresponding targets.

Two things to keep in mind, the right hand side evaluates the expression first. So on the RHS, we first create the tuple :

 (3, 4)

Note, that is done left to right. Now, the assignment to each target in the target list on the left is done in order:

arr[0] = 3

Then the next target, arr[0] is 3, and 3-1 is 2

arr[2] = 4

So a simple solution is to just to compute the indices first before the swap:

>>> arr = [4, 2, 1, 3]
>>> i, j = arr[0] - 1, 0
>>> arr[j], arr[i] = arr[i], arr[j]
>>> arr
[3, 2, 1, 4]

Here is a demonstration using a verbose list that we can define easily:

>>> class NoisyList(list):
...     def __getitem__(self, item):
...         value = super().__getitem__(item)
...         print("GETTING", item, "value of", value)
...         return value
...     def __setitem__(self, item, value):
...         print("SETTING", item, 'with', value)
...         super().__setitem__(item, value)
...
>>> arr = NoisyList([4, 2, 1, 3])
>>> arr[0], arr[arr[0]-1] = arr[arr[0]-1], arr[0]
GETTING 0 value of 4
GETTING 3 value of 3
GETTING 0 value of 4
SETTING 0 with 3
GETTING 0 value of 3
SETTING 2 with 4
10 months ago · Santiago Trujillo Report

0

Ok, this happens because the arr[0] is changed to 3 when you assign arr[arr[0]-1] to it. And after that, when python takes a look at arr[arr[0]-1] and tries to assign it the value of arr[0] if finds arr[0] to be 3 because in the previous assignment you have changed it. A small demonstration:

arr = [4, 2, 1, 3]

arr[0], arr[arr[0]-1] = arr[arr[0]-1], arr[0]
 │                           │ 
 └──────────┬────────────────┘
            │
            │
      these are done first 
      and the list becomes:
      [3, 2, 1, 3]

Next when the python takes a look at
                     these two, it:
                         │
            ┌────────────┴───────────────┐
            │                            │ 
arr[0], arr[arr[0]-1] = arr[arr[0]-1], arr[0]

it finds the `arr[0]` to be `3` so, it assigns `3` to the `3rd` 
element because `arr[arr[0]-1]` is 3.
10 months ago · Santiago Trujillo Report

0

The replacement of the two values isn't truly simultaneous; they are handled in order from left to right. So altering arr during that process is leading to this behavior.

Consider this alternative example:

>>> arr = [1, 2, 3]
>>> arr[0], arr[arr[0]] = 10, 5
...

With a hypothetical simultaneous reassignment, we try to replace the first value of arr with 10, and then the arr[0]th (aka 1st) element with 5. So hopefully, we get [10, 5, 3]. But this fails with IndexError: list assignment index out of range. If you then inspect arr after this error:

>>> arr
[10, 2, 3]

The first assignment was completed, but the second failed. When it came to the second assignment (after the comma), the actual arr[0]th (aka 10th) value cannot be found (b/c the list isn't that long).

This behavior can also be seen by clearly specifying the second assignment to fail (still by specifying an index out of range):

>>> arr = [1, 2, 3]
>>> arr[0], arr[99] = 5, 6
# Same index error, but arr becomes [5, 2, 3]

This feels reminiscent of modifying a list you are iterating over, which is sometimes doable but often discouraged because it leads to issues like what you are seeing.

One alternative is to create a copy (this is sometimes a solution for modifying the list you are iterating over), and use that for referencing values in arr:

>>> arr = [4, 2, 1, 3]
>>> copy = arr.copy()
>>> arr[0], arr[copy[0]-1] = copy[copy[0]-1], copy[0]
>>> arr
[3, 2, 1, 4]

Though it is pretty ugly here. The alternative in the accepted answer is much nicer, or this idiomatic approach should probably work as well!

10 months ago · Santiago Trujillo Report
Answer question
Find remote jobs