Что происходит в вырожденном случае множественного назначения?
Я обучение себе алгоритмы. Мне нужно было поменять два элемента в списке. Python упрощает все:
def swap(A, i, j):
A[i], A[j] = A[j], A[i]
Это работает:
>>> A = list(range(5))
>>> A
[0, 1, 2, 3, 4]
>>> swap(A, 0, 1)
>>> A
[1, 0, 2, 3, 4]
Обратите внимание, что функция устойчива к вырожденному случаю i = j
. Как и следовало ожидать, он просто оставляет список без изменений:
>>> A = list(range(5))
>>> swap(A, 0, 0)
>>> A
[0, 1, 2, 3, 4]
Позже я хотел переставить три элемента в списке. Я написал функцию, чтобы переставить их в 3-цикл:
def cycle(A, i, j, k):
A[i], A[j], A[k] = A[j], A[k], A[i]
Это сработало хорошо:
>>> A = list("tap")
>>> A
['t', 'a', 'p']
>>> cycle(A, 0, 1, 2)
>>> A
['a', 'p', 't']
Однако я (в конце концов) обнаружил, что он ошибочен в дегенеративных случаях. Я предположил, что вырожденный 3-цикл будет свопом. Таким образом, когда i = j
, cycle(i, i, k) ≡ swap(i, k)
:
>>> A = list(range(5))
>>> cycle(A, 0, 0, 1)
>>> A
[1, 0, 2, 3, 4]
Но когда i = k
происходит что-то еще:
>>> A = list(range(5))
>>> sum(A)
10
>>> cycle(A, 1, 0, 1)
>>> A
[1, 1, 2, 3, 4]
>>> sum(A)
11
Что происходит? sum
должен быть инвариантным относительно любой перестановки! Почему этот случай i = k
вырождается по-разному?
Как я могу достичь того, чего хочу? Это функция с тремя циклами, которая вырождается в swap, если только 2 индекса различны cycle(i, i, j) ≡ cycle(i, j, i) ≡ cycle(i, j, j) ≡ swap(i, j)
Ответы
Ответ 1
cycle
выполняет именно то, что вы просите: присваивание значениям левой руки значения правой руки.
def cycle(A, i, j, k):
A[i], A[j], A[k] = A[j], A[k], A[i]
функционально эквивалентно
def cycle(A, i, j, k):
new_values = A[j], A[k], A[i]
A[i], A[j], A[k] = new_values
Итак, когда вы делаете cycle(A, 1, 0, 1)
, вы говорите, что хотите
A[1] = previous_A[0]
A[0] = previous_A[1]
A[1] = previous_A[1]
Если вы хотите, чтобы цикл работал последовательно, вы должны писать его последовательно, иначе python оценивает правую руку, а затем расширяет его до аргументов в левой части.
Ответ 2
Ну, похоже, вы переназначаете одну и ту же цель A[1]
, чтобы получить визуализацию вызова:
A[1], A[0], A[1] = A[0], A[1], A[1]
Помните, что из документации по :
Оператор присваивания оценивает список выражений (помните, что это может быть одно выражение или список, разделенный запятыми, последний из которых имеет кортеж) и присваивает единственный результирующий объект каждому из целевых списков слева направо.
Итак, ваша оценка выглядит примерно так:
- Создайте кортеж со значениями
A[0], A[1], A[1]
, переведя на (0, 1, 1)
- Назначьте их целевому списку
A[1], A[0], A[1]
слева направо.
Назначение слева направо имеет место:
-
A[1] = 0
-
A[0] = 1
-
A[1] = 1
Итак, первое сделанное присваивание - это A[1]
с первым элементом кортежа 0
, затем второе присваивание A[0]
со вторым элементом 1
и, наконец, в конце, A[1]
переопределено с третьим элементом в кортеже 1
.
Вы можете получить более свернутое представление об этом с помощью dis.dis
; обратите внимание, как сначала загружаются все элементы в правой руке оператора присваивания, а затем присваиваются их значениям:
dis.dis(cycle)
2 0 LOAD_FAST 0 (A)
3 LOAD_FAST 2 (j)
6 BINARY_SUBSCR
7 LOAD_FAST 0 (A)
10 LOAD_FAST 3 (k)
13 BINARY_SUBSCR
14 LOAD_FAST 0 (A)
17 LOAD_FAST 1 (i)
20 BINARY_SUBSCR # Loading Done
21 ROT_THREE
22 ROT_TWO
23 LOAD_FAST 0 (A) # Assign first
26 LOAD_FAST 1 (i)
29 STORE_SUBSCR
30 LOAD_FAST 0 (A) # Assign second
33 LOAD_FAST 2 (j)
36 STORE_SUBSCR
37 LOAD_FAST 0 (A) # Assing third
40 LOAD_FAST 3 (k)
43 STORE_SUBSCR
44 LOAD_CONST 0 (None)
47 RETURN_VALUE
Ответ 3
Потому что cycle(A, 1, 0, 1)
становится A[1], A[0], A[1] = A[0], A[1], A[1]
, в результате чего оба A[0]
и A[1]
заканчиваются старым значением A[1]
. cycle(0, 0, 1)
работает, потому что он становится A[0], A[0], A[1] = A[0], A[1], A[0]
, что эквивалентно swap(A, k, j)
.