Что происходит в вырожденном случае множественного назначения?

Я обучение себе алгоритмы. Мне нужно было поменять два элемента в списке. 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).