J'ai rencontré ce problème quand je voulais essayer une version python de: https://leetcode.com/problems/first-missing-positive/discuss/17071/My-short-c++-solution-O(1 ) -space-and-O (n) -time
Je ne suis pas sûr de savoir pourquoi a[0], a[a[0]] = a[a[0]], a[0]
celui-ci ne fait pas l’échange?
>>> 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]
Je suppose que la mise en œuvre de a, b = b, une syntaxe ressemble à quelque chose comme:
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)
Puis j'ai vérifié l'implémentation de la fonction de swap en C++. Je ne connais rien au C++, mais on dirait que l'idée est la même: 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]);
}
nous avons c = a, alors a = b et b = aAlors pourquoi la fonction de swap C++ n’a pas ce problème? Et comment écrire ce type de fonction de swap de façon Pythonic?
Pour comprendre cela, vous devez entrer dans l'implémentation en utilisant dis
.
Tout d’abord, considérons une fonction d’échange simple:
from dis import dis
def swap(i, j):
i, j = j, i
dis(swap)
Code d'octet de sortie:
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
Vous pouvez voir qu'il y a ROT_TWO ce qui signifie que
Échange les deux éléments de pile les plus haut.
Ce ROT_TWO
est principalement responsable de la permutation.
J'en viens maintenant à votre question:
Prenons le exemple qui fonctionne:
from dis import dis
def swap():
a = [2, 1]
a[0], a[1] = a[1], a[0]
dis(swap)
Code d'octet de sortie:
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
Le code d'octet de sortie est similaire à ce que nous avons quand il s'agit d'une simple fonction d'échange.
Mais lorsque le code est modifié:
from dis import dis
def swap():
a = [1, 0]
a[0], a[a[0]] = a[a[0]], a[0]
dis(swap)
swap()
La sortie est
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
Vous pouvez voir le code d'octet de sortie que les deux éléments supérieurs sont identiques. Par conséquent, il ne change pas
Ce type de comportement est en effet lié à la façon dont Python évalue l'expression du type
a,b=b,a
En fait, ce que fait Python, c'est d'abord "préparer" les valeurs du côté droit en créant un Tuple (b,a)
. Ensuite, ce tuple est décompressé et affecté aux variables dans l'ordre inverse.
Il est important de noter que le nuplet temporaire est créé en utilisant values de variables (copies de facto de valeurs) et non pas references aux variables (vous pouvez lire ici à propos de la différence entre le passage par valeur et par référence ).
Pour décomposer l'exemple avec les types de référence (listes) que vous avez utilisés:
a = [2,1,0]
a[0], a[a[0]] = a[a[0]], a[0]
a[a[0]]
prend la valeur de l'élément a[0]
(égal à 2
) de la liste a
(valeur 0
).a[0]
est 2
et le tuple créé est donc (0,2)
(0,2)
est décompressé et 0
remplace 2
dans la liste (0ème élément).a[a[0]]
peut être lu comme suit: prenez le 0ème élément de la liste a
(qui est actuellement 0
), puis remplacez la valeur de la liste à cet endroit par 2
à partir de la décompression de Tuple (0
est remplacé par 2
- ce qui donne à l'opération l'apparence il ne fait rien à la liste).Comme suggéré dans la réponse de von Oak
changer l'ordre aide, car l'étape à partir du point 4. ci-dessus ne remplace pas la valeur à nouveau.
Python et C++ sont des langages différents avec des règles différentes. C'est en grande partie la raison pour laquelle des constructions superficiellement similaires se comportent différemment dans ces langages.
Vous ne pouvez pas écrire une variable swap
générique en Python qui fonctionnerait avec une entrée telle que a[0], a[a[0]]
. Ce n'est pas un problème. Vous ne devriez jamais essayer d'échanger de telles choses dans n'importe quelle langue afin d'éviter toute confusion et d'améliorer vos chances d'obtenir un futur emploi en tant que programmeur.
Si vous devez absolument échanger des éléments de tableau indexés par des éléments du même tableau, vous pouvez le faire comme en Python:
p, q, a[p], a[q] = index0, index1, a[q], a[p]
où index0
et index1
peuvent être toute expression impliquant a[i]
, a[a[i]]
, a[a[a[i]]]
ou quelque chose de similaire. Par exemple
p, q, a[p], a[q] = a[0], a[a[0]], a[q], a[p]
travaux.
Il est facile d'y penser uniquement sur papier (par exemple, lors de l'entretien d'embauche) et vous n'avez pas besoin de déboguer ou de désassembler le code en bytecode pour le comprendre.
Je pense aussi que cela n'a rien à voir avec l'implémentation de la fonction de swap en C++. Ce sont des choses sans rapport.
Ce que vous devez seulement savoir, c'est que le côté droit est entièrement évalué en premier, puis que les valeurs du côté droit de l'expression sont attribuées aux valeurs du côté gauche dans l'ordre de gauche à droite. Sophros a répondu correctement J'élargis seulement l'idée plus loin et plus en détail.
Imagine le premier cas. On a:
a = [2,1,0]
a[0], a[a[0]] = a[a[0]], a[0]
Lorsque nous commençons à exécuter ce code, le côté droit évalue en premier, nous aurons donc
a[0], a[a[0]] = a[a[0]], a[0] # a[a[0]] == 0, a[0] == 2, a == [2, 1, 0]
Sur le côté droit, nous avons Tuple (0, 2)
et a
est toujours [2, 1, 0]
Ensuite, nous commençons à assigner le côté gauche de l'expression à partir de la gauche, ainsi au a[0]
nous assignons le premier élément du tuple, qui est 0
. Maintenant nous avons
a[0], a[a[0]] = (0, 2) # a[0] == 0, a == [0, 1, 0]
Et maintenant, nous exécutons la dernière partie de l'affectation, à savoir a[a[0]]
assigner 2
. Mais a[0]
est maintenant 0
, donc après réduction, nous attribuons à a[0]
valeur 2
. Par conséquent, les valeurs après la dernière affectation sont
a[0], a[a[0]] = (0, 2) # a[a[0]] == 2, a == [2, 1, 0]
Ce qui semble que rien n’a changé et que les valeurs n’ont pas été permutées, mais comme il ressort de ce qui précède a
était [2,1,0]
, puis [0,1,0]
et enfin [2,1,0]
. Il semble donc que rien n’a changé et que l’échange ne fonctionne pas.
Et maintenant le deuxième cas, où nous ne changeons que l'ordre des variables dans l'expression:
a = [2,1,0]
a[a[0]], a[0] = a[0], a[a[0]]
Lorsque nous commençons à exécuter ce code, le côté droit évalue en premier, nous aurons donc
a[a[0]], a[0] = a[0], a[a[0]] # a[0] == 2, a[a[0]] == 0, a == [2, 1, 0]
Sur le côté droit, nous avons Tuple (2, 0)
et a
est toujours [2, 1, 0]
Ensuite, nous commençons à assigner le côté gauche de l'expression à partir de la gauche, ainsi au a[a[0]]
nous assignons le premier élément du tuple, qui est 2
. a[0]
est 2
, donc après réduction, nous attribuons à a[2]
valeur 2
. Maintenant nous avons
a[a[0]], a[0] = (2, 0) # a[a[0]] == 2, a == [2, 1, 2]
Et maintenant, nous exécutons la dernière partie de l'affectation, à savoir a[0]
assigner 0
. Par conséquent, les valeurs après la dernière affectation sont
a[a[0]], a[0] = (2, 0) # a[0] == 0, a == [0, 1, 2]
Maintenant, cela fonctionne comme prévu.
Il est donc nécessaire de penser également à l'ordre dans lequel vous avez des variables dépendantes dans votre expression d'échange. En tant que variables dépendantes, je veux dire que dans le premier cas, nous avons à gauche a[0], a[a[0]]
, ce qui signifie que a[0]
change sa valeur et que a[a[0]]
utilise cette valeur modifiée, ce qui entraîne un comportement indésirable.
Enfin, quel que soit le langage de programmation, il est préférable de ne pas utiliser de variables dépendantes (index de tableau pour un autre index de tableau, etc.), lorsque vous souhaitez échanger leurs valeurs.