107 votes

Échanger la valeur de deux variables sans utiliser la troisième variable

Une des questions très délicates posées lors d'un entretien.

Échangez les valeurs de deux variables comme a=10 et b=15 .

Généralement, pour échanger les valeurs de deux variables, nous avons besoin d'une troisième variable :

temp=a;
a=b;
b=temp;

Maintenant, l'exigence est d'échanger les valeurs de deux variables sans utiliser la troisième variable.

81 votes

Wow. Une question d'entretien mal choisie, à mon avis. Il s'agit d'une technique qui est rarement, voire jamais, utile en pratique. Il y a de fortes chances qu'elle ne fasse qu'embrouiller l'optimiseur du compilateur, ce qui aboutira à un code moins efficace qu'un "swap temporaire". À moins que l'endroit où vous avez passé votre entretien ne soit impliqué dans des activités très mathématiques (par exemple, le développement d'algorithmes de cryptage), je ne vois pas de raison valable de poser une telle question.

10 votes

En effet, cette question ne vous dit rien d'autre que si le candidat connaît cette astuce particulière qui est pratiquement inutile dans un code de production. Je suppose que vous pouvez tomber sur un magicien occasionnel qui trouve une solution à la volée, mais les personnes de ce genre qui ne connaissent pas déjà l'astuce sont probablement assez rares.

2 votes

Peut-être veulent-ils éliminer les personnes qui pensent que la connaissance de ces astuces est ce qui fait un bon programmeur ? De plus, lorsque vous lisez des articles sur l'astuce xor, faites attention aux moments où elle échoue (ce qui, selon moi, la rend complètement inutile pour l'échange d'entiers à usage général).

159voto

Yacoby Points 29771

Utilisation de la Algorithme d'échange xor

void xorSwap (int* x, int* y) {
    if (x != y) { //ensure that memory locations are different
       *x ^= *y;
       *y ^= *x;
       *x ^= *y;
    }
}

Pourquoi ce test ?

Le test consiste à s'assurer que x et y ont des emplacements mémoire différents (plutôt que des valeurs différentes). Cela est dû au fait que (p xor p) = 0 et si x et y partagent le même emplacement mémoire, lorsque l'un est mis à 0, les deux sont mis à 0. Lorsque *x et *y sont tous deux égaux à 0, toutes les autres opérations xor sur *x et *y seront égales à 0 (puisqu'elles sont identiques), ce qui signifie que la fonction mettra tous deux *x et *y à 0.

S'ils ont les mêmes valeurs mais pas le même emplacement mémoire, tout fonctionne comme prévu.

*x = 0011
*y = 0011
//Note, x and y do not share an address. x != y

*x = *x xor *y  //*x = 0011 xor 0011
//So *x is 0000

*y = *x xor *y  //*y = 0000 xor 0011
//So *y is 0011

*x = *x xor *y  //*x = 0000 xor 0011
//So *x is 0011

Faut-il l'utiliser ?

Dans la plupart des cas, non. Le compilateur optimisera la variable temporaire et, étant donné que le swapping est une procédure courante, il devrait produire le code machine optimal pour votre plate-forme.

Prenez par exemple ce programme de test rapide écrit en C.

#include <stdlib.h>
#include <math.h>

#define USE_XOR 

void xorSwap(int* x, int *y){
    if ( x != y ){
        *x ^= *y;
        *y ^= *x;
        *x ^= *y;
    }
}

void tempSwap(int* x, int* y){
    int t;
    t = *y;
    *y = *x;
    *x = t;
}

int main(int argc, char* argv[]){
    int x = 4;
    int y = 5;
    int z = pow(2,28); 
    while ( z-- ){
#       ifdef USE_XOR
            xorSwap(&x,&y);
#       else
            tempSwap(&x, &y);
#       endif
    }
    return x + y;    
}

Compilé en utilisant :

gcc -Os main.c -o swap

La version xor prend

real    0m2.068s
user    0m2.048s
sys  0m0.000s

Alors que la version avec la variable temporaire prend :

real    0m0.543s
user    0m0.540s
sys  0m0.000s

1 votes

Il peut être utile d'expliquer le pourquoi du test (c'est-à-dire que l'approche triple xor échoue terriblement si x et y font référence au même objet).

1 votes

Je ne sais pas comment ça a pu avoir autant de votes positifs alors que le code est tellement cassé. Les deux échanges dans le segment de code testé sont complètement incorrects. Regarde bien.

0 votes

@SoapBox : Très bon point ! L'échange XOR met x à zéro et laisse y intact, et l'échange temporaire laisse les deux avec la valeur de x. Oups !

94voto

jk. Points 8019

La forme générale est :

A = A operation B
B = A inverse-operation B
A = A inverse-operation B 

Cependant, vous devez potentiellement faire attention aux débordements et toutes les opérations n'ont pas un inverse bien défini pour toutes les valeurs pour lesquelles l'opération est définie. Par exemple, * et / fonctionnent jusqu'à ce que A ou B soit égal à 0.

xor est particulièrement agréable car il est défini pour tous les ints et est son propre inverse.

0 votes

XOR(X, X) == 0, donc xor fonctionne pour les ints SAUF lorsque les deux valeurs échangées sont égales. Je ne suis pas le premier à le faire remarquer et trop de personnes l'ont dit (ici et ailleurs) pour que l'on puisse en attribuer le mérite à quelqu'un en particulier.

85voto

Dor Points 3731
a = a + b
b = a - b // b = a
a = a - b

2 votes

@Alok : Ce n'est pas pris en considération, donc ce n'est pas pratique :)

4 votes

Si a et b sont de mêmes types d'entiers de taille basique (comme int , unsigned short ...), cela fonctionne toujours à la fin, même avec un débordement, car si a + b déborde, alors a - b sera en dessous. Avec ces types de nombres entiers de base, les valeurs ne font que s'inverser.

11 votes

En C, en utilisant ceci sur unsigned Les types d'entiers sont corrects et fonctionnent toujours. Pour les types signés, cela entraînerait un comportement non défini en cas de dépassement.

81voto

Charles Bailey Points 244082

Personne n'a suggéré d'utiliser std::swap mais pas encore.

std::swap(a, b);

I n'utilisent pas de variables temporaires et selon le type de a et b l'implémentation peut avoir une spécification qui ne le fait pas non plus. L'implémentation doit être écrite en sachant si une "astuce" est appropriée ou non. Il ne sert à rien d'essayer de deviner.

Plus généralement, je voudrais probablement faire quelque chose comme ça, car cela fonctionnerait pour les types de classe permettant à ADL de trouver une meilleure surcharge si possible.

using std::swap;
swap(a, b);

Bien entendu, la réaction de l'intervieweur à cette réponse peut en dire long sur le poste à pourvoir.

0 votes

Bien sûr, dans C++0x, swap utilisera des références rvalue, ce qui sera encore mieux !

19 votes

Tout le monde veut montrer le brillant xor ou toute autre astuce qu'il a appris, avec diverses mises en garde sur la façon de l'utiliser, mais ceci est indubitablement la meilleure réponse.

2 votes

Pourquoi pas std::swap(a, b) ; ? Je veux dire pourquoi utiliser ?

19voto

Vilx- Points 37939

Comme l'a déjà noté Manu, l'algorithme XOR est un algorithme populaire qui fonctionne pour toutes les valeurs entières (y compris les pointeurs, avec un peu de chance et un casting). Par souci d'exhaustivité, j'aimerais mentionner un autre algorithme moins puissant avec l'addition/soustraction :

A = A + B
B = A - B
A = A - B

Ici, il faut faire attention aux débordements et aux sous-exécutions, mais sinon, cela fonctionne tout aussi bien. Vous pouvez même l'essayer sur des flottants/doubles au cas où XOR n'est pas autorisé sur ceux-ci.

0 votes

" toutes les valeurs entières (ce qui inclut les pointeurs alors) " -- Quoi ? Non, les pointeurs ne sont pas des entiers, et vous ne pouvez pas trier les valeurs des pointeurs. Vous ne pouvez pas non plus trier des valeurs à virgule flottante. Mais la méthode d'addition/soustraction a un comportement indéfini (pour les types signés ou à virgule flottante) en cas de débordement ou de sous-débordement, peut perdre de la précision pour les virgules flottantes, et ne peut pas être appliquée aux pointeurs ou à d'autres types non numériques.

0 votes

@KeithThompson - OK, la perte de précision pour les points flottants est vraie, mais selon les circonstances, elle pourrait être acceptable. En ce qui concerne les pointeurs, je n'ai jamais entendu parler d'un compilateur ou d'une plate-forme où les pointeurs ne pouvaient pas être librement convertis en entiers et inversement (en prenant soin de choisir un entier de la bonne taille, bien sûr). Mais bon, je ne suis pas un expert en C/C++. Je sais que cela va à l'encontre de la norme, mais j'avais l'impression que ce comportement était plutôt cohérent dans... tout.

1 votes

@Vilx- : Pourquoi la perte de précision serait-elle acceptable alors qu'il est si facile de l'éviter en utilisant une variable temporaire -- ou std::swap() ? Bien sûr, vous pouvez convertir vers des entiers et inversement (en supposant qu'il existe un type d'entier suffisamment grand, ce qui n'est pas garanti), mais ce n'est pas ce que vous avez suggéré ; vous avez laissé entendre que les pointeurs sont entiers, et non qu'ils peuvent être convertis en entiers. Oui, la réponse acceptée ne fonctionne pas pour les pointeurs. Réponse de Charles Bailey en utilisant std::swap est vraiment la seule correcte.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X