web-dev-qa-db-fra.com

python a, b = b, une implémentation? Comment est-il différent de la fonction de swap C++?

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?

15
RioAraki

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

5
argo

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]
  1. a[a[0]] prend la valeur de l'élément a[0] (égal à 2) de la liste a (valeur 0).
  2. a[0] est 2 et le tuple créé est donc (0,2)
  3. Le tuple (0,2) est décompressé et 0 remplace 2 dans la liste (0ème élément).
  4. Maintenant, 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.

7
sophros

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]

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.

1
n.m.

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.

0
von Oak