python a, b = b, реализация? Как он отличается от функции подкачки C++?
Я столкнулся с этой проблемой, когда хочу попробовать версию python: https://leetcode.com/problems/first-missing-positive/discuss/17071/My-short-C++-solution-O(1)-space и-О (п) -time
Я не уверен, почему a[0], a[a[0]] = a[a[0]], a[0]
это не делает обмен?
>>> nums
[2, 1, 0]
>>> a = [2,1,0]
>>> a[0], a[a[0]] = a[a[0]], a[0]
>>> a
[2, 1, 0]
>>> a[0]
2
>>> a[0],a[2] = a[2], a[0]
>>> a
[0, 1, 2]
Я предполагаю, что реализация синтаксиса a, b = b - это что-то вроде:
tmp = a[0] (tmp = 2)
a[0] = a[a[0]] (a[0] = a[2] = 0)
a[a[0]] = tmp (a[a[0]] = a[0] = tmp = 2)
Затем я проверил реализацию функции swap в C++. Я ничего не знаю о C++, но похоже, что идея такая же: http://www.cplusplus.com/reference/algorithm/swap/
The behavior of these function templates is equivalent to:
template <class T> void swap (T& a, T& b)
{
T c(std::move(a)); a=std::move(b); b=std::move(c);
}
template <class T, size_t N> void swap (T (&a)[N], T (&b)[N])
{
for (size_t i = 0; i<N; ++i) swap (a[i],b[i]);
}
мы имеем c = a, тогда a = b и b = a Итак, почему функция C++ swap не имеет этой проблемы? И как написать такую функцию подкачки в питоническом режиме?
Ответы
Ответ 1
Чтобы понять это, вам нужно войти в реализацию, используя dis
.
Сначала рассмотрим простую функцию подкачки:
from dis import dis
def swap(i, j):
i, j = j, i
dis(swap)
Выходной байт-код:
4 0 LOAD_FAST 1 (j)
2 LOAD_FAST 0 (i)
4 ROT_TWO
6 STORE_FAST 0 (i)
8 STORE_FAST 1 (j)
10 LOAD_CONST 0 (None)
12 RETURN_VALUE
Вы можете видеть, что есть ROT_TWO, что означает, что
Своп меняет два самых больших элемента стека.
Этот ROT_TWO
несет ROT_TWO
ответственность за обмен.
Теперь подошел к вашему вопросу:
Давайте возьмем пример, который работает:
from dis import dis
def swap():
a = [2, 1]
a[0], a[1] = a[1], a[0]
dis(swap)
Выходной байт-код:
4 0 LOAD_CONST 1 (2)
2 LOAD_CONST 2 (1)
4 BUILD_LIST 2
6 STORE_FAST 0 (a)
5 8 LOAD_FAST 0 (a)
10 LOAD_CONST 2 (1)
12 BINARY_SUBSCR
14 LOAD_FAST 0 (a)
16 LOAD_CONST 3 (0)
18 BINARY_SUBSCR
20 ROT_TWO
22 LOAD_FAST 0 (a)
24 LOAD_CONST 3 (0)
26 STORE_SUBSCR
28 LOAD_FAST 0 (a)
30 LOAD_CONST 2 (1)
32 STORE_SUBSCR
34 LOAD_CONST 0 (None)
36 RETURN_VALUE
Выходной байтовый код похож на то, что мы имеем, когда оно является простой функцией подкачки.
Но когда код изменен:
from dis import dis
def swap():
a = [1, 0]
a[0], a[a[0]] = a[a[0]], a[0]
dis(swap)
swap()
Выход
4 0 LOAD_CONST 1 (1)
2 LOAD_CONST 2 (0)
4 BUILD_LIST 2
6 STORE_FAST 0 (a)
5 8 LOAD_FAST 0 (a)
10 LOAD_FAST 0 (a)
12 LOAD_CONST 2 (0)
14 BINARY_SUBSCR
16 BINARY_SUBSCR
18 LOAD_FAST 0 (a)
20 LOAD_CONST 2 (0)
22 BINARY_SUBSCR
24 ROT_TWO
26 LOAD_FAST 0 (a)
28 LOAD_CONST 2 (0)
30 STORE_SUBSCR
32 LOAD_FAST 0 (a)
34 LOAD_FAST 0 (a)
36 LOAD_CONST 2 (0)
38 BINARY_SUBSCR
40 STORE_SUBSCR
42 LOAD_CONST 0 (None)
44 RETURN_VALUE
Вы можете видеть выходной байтовый код, что два верхних элемента одинаковы. Следовательно, он не меняет местами
Ответ 2
Такое поведение действительно связано с тем, как Python оценивает выражение типа
a,b=b,a
Фактически, что делает Python, сначала он "подготавливает" значения правой стороны, создавая кортеж (b,a)
. Затем этот кортеж распаковывается и присваивается переменным в обратном порядке.
Важно отметить, что временный кортеж создается с использованием значений переменных (фактические копии значений), а не ссылки на переменные (здесь вы можете прочитать о различии между передачей по значению и ссылкой).
Чтобы разбить пример со ссылочными типами (списками), которые вы использовали:
a = [2,1,0]
a[0], a[a[0]] = a[a[0]], a[0]
-
a[a[0]]
принимает значение из элемента a[0]
(равного 2
) списка a
(значение 0
). -
a[0]
равно 2
поэтому созданный набор состоит из (0,2)
- Tuple
(0,2)
распаковывается и 0
заменяет 2
в списке (0-й элемент). - Теперь
a[a[0]]
можно прочитать как: взять 0-й элемент списка a
(который в настоящее время равен 0
), а затем заменить значение в списке в этом месте на 2
из распаковки кортежа (теперь 0
заменяется на 2
- которые заставляют операцию выглядеть так, как будто она ничего не делает для списка).
Как было предложено в ответе от von Oak
изменение порядка помогает, потому что шаг из пункта 4. выше не заменяет значение снова.
Ответ 3
Python и C++ - это разные языки с разными правилами. Это в значительной степени причина, по которой внешне подобные конструкции ведут себя по-разному на этих языках.
Вы не можете написать общий swap
в Python, который будет работать со входом, например a[0], a[a[0]]
. Это не является проблемой. Вы никогда не должны пытаться менять такие вещи на любом языке, чтобы избежать путаницы и улучшить свои шансы на будущую работу в качестве программиста.
Если вам абсолютно необходимо обменивать элементы массива, индексированные элементами одного и того же массива, вы можете сделать это так на Python:
p, q, a[p], a[q] = index0, index1, a[q], a[p]
где index0
и index1
может быть любое выражение, включающее a[i]
, a[a[i]]
, a[a[a[i]]]
или что - либо подобное. Например
p, q, a[p], a[q] = a[0], a[a[0]], a[q], a[p]
работает.
Ответ 4
Легко думать об этом также только на бумаге (например, на собеседовании), и вам не нужно отлаживать или дизассемблировать код для байт-кода для понимания.
Я также думаю, что это не имеет ничего общего с реализацией функции swap в C++. Это не связанные вещи.
То, что вам нужно знать только, состоит в том, что правая часть сначала оценивается полностью, а затем значения с правой стороны выражения присваиваются значениям слева в порядке слева направо. Sophros ответил на это правильно, я только расширяю идею дальше и более подробно.
Представьте первый случай. У нас есть:
a = [2,1,0]
a[0], a[a[0]] = a[a[0]], a[0]
Когда мы начнем выполнять этот код, правая сторона сначала оценивает, поэтому мы будем иметь
a[0], a[a[0]] = a[a[0]], a[0] # a[a[0]] == 0, a[0] == 2, a == [2, 1, 0]
С правой стороны мы имеем кортеж (0, 2)
а a
все еще [2, 1, 0]
Затем мы начинаем назначать левую часть выражения слева, поэтому к a[0]
мы назначаем первый элемент из кортежа, который равен 0
. Теперь у нас есть
a[0], a[a[0]] = (0, 2) # a[0] == 0, a == [0, 1, 0]
И теперь мы выполняем последнюю часть задания, которая относится к a[a[0]]
присваиванию 2
. Но a[0]
теперь 0
, поэтому после редукции мы присваиваем значение a[0]
2
. Поэтому значения после последнего присваивания
a[0], a[a[0]] = (0, 2) # a[a[0]] == 2, a == [2, 1, 0]
Который, кажется, что ничего не изменилось, и значения не менять, но, как видно из выше был a
[2,1,0]
, затем [0,1,0]
и, наконец, снова [2,1,0]
. Кажется, ничего не изменилось, и обмен не работает.
А теперь второй случай, когда мы только изменяем порядок переменных в выражении:
a = [2,1,0]
a[a[0]], a[0] = a[0], a[a[0]]
Когда мы начнем выполнять этот код, правая сторона сначала оценивает, поэтому мы будем иметь
a[a[0]], a[0] = a[0], a[a[0]] # a[0] == 2, a[a[0]] == 0, a == [2, 1, 0]
Справа мы имеем кортеж (2, 0)
а a
все еще [2, 1, 0]
Затем мы начинаем присваивать левой стороне выражения слева, поэтому для a[a[0]]
мы назначаем первый элемент из кортежа, который равен 2
. a[0]
равно 2
, поэтому после сокращения мы присваиваем значение a[2]
2
. Теперь у нас есть
a[a[0]], a[0] = (2, 0) # a[a[0]] == 2, a == [2, 1, 2]
И теперь мы выполняем последнюю часть задания, которая назначается 0
a[0]
. Поэтому значения после последнего присваивания
a[a[0]], a[0] = (2, 0) # a[0] == 0, a == [0, 1, 2]
Теперь это работает так, как ожидалось.
Поэтому необходимо также подумать о порядке, когда у вас есть зависимые переменные в вашем выражении подкачки. В качестве зависимых переменных я имею в виду, что в первом случае мы имеем в левой части a[0], a[a[0]]
что означает a[0]
изменить его значение, а a[a[0]]
использовать это измененное значение, что приводит к нежелательному поведению.
Наконец, независимо от языка программирования, лучше не использовать зависимые переменные (индекс массива для другого индекса массива и т.д.), Когда вы хотите поменять свои значения.