Zamiana wartości dwóch zmiennych bez użycia trzeciej zmiennej

104

Jedno z bardzo trudnych pytań zadawanych w wywiadzie.

Zamień wartości dwóch zmiennych, takich jak a=10i b=15.

Generalnie, aby zamienić wartości dwóch zmiennych, potrzebujemy trzeciej zmiennej, takiej jak:

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

Teraz wymaganiem jest, zamień wartości dwóch zmiennych bez używania trzeciej zmiennej.

Muhammad Akhtar
źródło
76
Łał. Źle dobrane pytanie do wywiadu IMHO. Jest to technika rzadko, jeśli w ogóle, przydatna w praktyce. Istnieje duża szansa, że ​​tylko zmyli optymalizator kompilatora, co spowoduje, że kod będzie mniej wydajny niż „tymczasowa zamiana”. Chyba że to miejsce, w którym rozmawiałeś, nie angażuje się w bardzo ciężką matematykę (pomyśl: rozwój algorytmu szyfrowania itp.) Nie wyobrażam sobie żadnego dobrego powodu, aby zadać takie pytanie.
Dan Molding
10
Rzeczywiście, to pytanie nie mówi nic poza tym, czy kandydat zna tę konkretną sztuczkę, która jest prawie bezużyteczna w kodzie produkcyjnym. Przypuszczam, że od czasu do czasu można spotkać czarodzieja, który odkrywa to w locie, ale tacy ludzie, którzy jeszcze nie znają tej sztuczki, mogą być dość rzadcy.
dyrektor generalny
2
Może chcą wyeliminować ludzi, którzy myślą, że znajomość takich sztuczek jest tym, co czyni dobrego programistę? Ponadto, czytając o sztuczce xor, zwróć uwagę na to, kiedy się nie powiedzie (co sprawia, że ​​IMO jest prawie całkowicie bezużyteczne przy zamianie liczb całkowitych ogólnego przeznaczenia).
UncleBens

Odpowiedzi:

155

Korzystanie z algorytmu wymiany xor

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


Dlaczego test?

Test ma na celu upewnienie się, że x i y mają różne lokalizacje pamięci (zamiast różnych wartości). Dzieje się tak, ponieważ (p xor p) = 0i jeśli oba x i y mają tę samą lokalizację pamięci, gdy jeden jest ustawiony na 0, oba są ustawione na 0. Gdy oba * x i * y mają wartość 0, wszystkie inne operacje xor na * x i * y będą równe 0 (ponieważ są takie same), co oznacza, że ​​funkcja ustawi zarówno * x, jak i * y na 0.

Jeśli mają te same wartości, ale nie to samo miejsce w pamięci, wszystko działa zgodnie z oczekiwaniami

*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


Czy powinno to być używane?

W ogólnych przypadkach nie. Kompilator zoptymalizuje zmienną tymczasową i biorąc pod uwagę, że zamiana jest powszechną procedurą, powinien wypisać optymalny kod maszynowy dla twojej platformy.

Weźmy na przykład ten szybki program testowy napisany w 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;    
}

Skompilowano przy użyciu:

gcc -Os main.c -o swap

Wersja xor trwa

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

Gdzie jako wersja ze zmienną tymczasową przyjmuje:

real    0m0.543s
user    0m0.540s
sys  0m0.000s
Yacoby
źródło
1
Może warto wyjaśnić, dlaczego test został przeprowadzony (tj. Że podejście potrójnego xor zawodzi strasznie, jeśli x i y odnoszą się do tego samego obiektu).
dmckee --- kociak byłego moderatora
1
Nie wiem, skąd wzięło się tak wiele głosów za, kiedy kod jest tak uszkodzony. Obie zamiany w testowanym segmencie kodu są całkowicie niepoprawne. Przyjrzyj się uważnie.
SoapBox
@SoapBox: Bardzo dobra uwaga! Zamiana XOR zeruje x & pozostawia y nietkniętą, a zamiana tymczasowa pozostawia oba z wartością x. Ups!
Drew Hall
4
@SoapBox Dobry chwyt. Naprawiono i ponownie przetestowałem i nie dostaję żadnej większej różnicy w czasie.
Yacoby
4
Pytający powiedział dwie zmienne, a nie int. : P
Plumenator
93

ogólna forma to:

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

jednak trzeba potencjalnie uważać na przepełnienia, a także nie wszystkie operacje mają odwrotność, która jest dobrze zdefiniowana dla wszystkich wartości, które są zdefiniowane. np. * i / działa, aż A lub B będzie równe 0

xor jest szczególnie przyjemny, ponieważ jest zdefiniowany dla wszystkich int i jest jego własną odwrotnością

jk.
źródło
XOR (X, X) == 0, więc xor działa dla ints Z WYJĄTKIEM, gdy dwie zamieniane wartości są równe. Nie jestem pierwszym, który to zwrócił uwagę, a zbyt wielu powiedziało to (tu i gdzie indziej), by wyróżnić kogokolwiek na kredyt.
AlanK,
85
a = a + b
b = a - b // b = a
a = a - b
Dor
źródło
16
Co jeśli się a+bprzepełnia?
Alok Singhal
2
@Alok: To nie jest brane pod uwagę, więc jest niepraktyczne :)
Dor
4
Jeśli A i B są takie same, podstawowe wielkości typów, Integer (jak int, unsigned short...), to nadal działa w końcu, nawet z przelewem, bo jeśli a + b przelewa, a następnie - b będzie niedopełniony. W przypadku tych podstawowych typów całkowitych wartości są po prostu przenoszone.
Patrick Johnmeyer
11
W C używanie tego na unsignedtypach całkowitych jest OK i działa zawsze. W przypadku typów podpisanych wywołałoby to niezdefiniowane zachowanie w przypadku przepełnienia.
jpalecek
2
@Shahbaz: Nawet jeśli liczby całkowite ze znakiem są przechowywane w uzupełnieniu do dwóch, zachowanie przy przepełnieniu jest nadal niezdefiniowane.
Keith Thompson,
79

Nikt jeszcze nie zasugerował używania std::swap.

std::swap(a, b);

Nie używam żadnych zmiennych tymczasowych iw zależności od typu ai bimplementacji może mieć specjalizację, która też nie. Implementację należy napisać, wiedząc, czy „sztuczka” jest odpowiednia, czy nie. Nie ma sensu próbować odgadnąć.

Mówiąc bardziej ogólnie, prawdopodobnie chciałbym zrobić coś takiego, ponieważ zadziałałoby to dla typów klas umożliwiających ADL znalezienie lepszego przeciążenia, jeśli to możliwe.

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

Oczywiście reakcja ankietera na tę odpowiedź może wiele powiedzieć o wakacie.

CB Bailey
źródło
oczywiście w C ++ 0x swap będzie używał referencji rvalue, więc będzie jeszcze lepszy!
jk.
16
Każdy chce pochwalić się błyszczącym xor lub inną sztuczką, której się nauczył, z różnymi zastrzeżeniami, jak go używać, ale to niewątpliwie najlepsza odpowiedź.
2
dlaczego nie std :: swap (a, b); ? Mam na myśli dlaczego używanie?
Dinaiz
2
@Dinaiz bez implementacji std::zdefiniowanych swapprzez użytkownika dla typów zdefiniowanych przez użytkownika są również dostępne do wywołania (to oznacza „zadziałałoby dla typów klas umożliwiających ADL znalezienie lepszego przeciążenia, jeśli to możliwe”).
Kyle Strand
std::swapużywa dodatkowej pamięci tymczasowej w swojej implementacji nie? cplusplus.com/reference/utility/swap
Ninja
19

Jak już zauważył manu, algorytm XOR jest popularnym algorytmem, który działa dla wszystkich wartości całkowitych (które zawierają wtedy wskaźniki, przy odrobinie szczęścia i rzutowaniu). Ze względu na kompletność chciałbym wspomnieć o innym mniej wydajnym algorytmie z dodawaniem / odejmowaniem:

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

Tutaj musisz uważać na przepełnienia / niedomiary, ale poza tym działa równie dobrze. Możesz nawet spróbować tego na pływakach / podwójnych w przypadku, gdy XOR nie jest na nich dozwolony.

Vilx-
źródło
wszystkie wartości całkowite (w tym wtedy wskaźniki) ” - co? Nie, wskaźniki nie są liczbami całkowitymi i nie można xor wartości wskaźnika. Nie możesz również xor wartości zmiennoprzecinkowych. Jednak metoda dodawania / odejmowania ma niezdefiniowane zachowanie (dla typów ze znakiem lub zmiennoprzecinkowym) w przypadku przepełnienia lub niedomiaru, może stracić precyzję dla zmiennoprzecinkowych i nie można jej zastosować do wskaźników ani innych typów nieliczbowych.
Keith Thompson,
@KeithThompson - OK, utrata precyzji dla zmiennoprzecinkowych jest prawdą, ale w zależności od okoliczności może być akceptowalna. Jeśli chodzi o wskaźniki - cóż, nigdy nie słyszałem o kompilatorze / platformie, w której wskaźniki nie mogłyby być dowolnie rzutowane na liczby całkowite iz powrotem (oczywiście zwracając uwagę, aby wybrać liczbę całkowitą o odpowiednim rozmiarze). Ale nie jestem ekspertem od C / C ++. Wiem, że jest to sprzeczne ze standardem, ale miałem wrażenie, że takie zachowanie jest dość spójne we ... wszystkim.
Vilx-
1
@ Vilx-: Dlaczego utrata precyzji byłaby akceptowalna, skoro tak łatwo można jej uniknąć, używając zmiennej tymczasowej - lub std::swap()? Jasne, możesz konwertować wskaźniki na liczby całkowite iz powrotem (zakładając, że istnieje wystarczająco duży typ liczby całkowitej, co nie jest gwarantowane), ale nie to sugerowałeś; zasugerowałeś, że wskaźniki liczbami całkowitymi, a nie, że można je przekształcić w liczby całkowite. Tak, zaakceptowana odpowiedź nie będzie działać w przypadku wskaźników. Odpowiedź Charles Bailey użyciu std::swapjest naprawdę jedyny słuszny.
Keith Thompson,
1
Przede wszystkim pytanie brzmiało „jak zamienić się bez użycia trzeciej zmiennej”, a powodem tego było „pytanie z wywiadu”. Podałem tylko jedną możliwą odpowiedź. Po drugie, OK, przepraszam za zasugerowanie, że wskaźniki są liczbami całkowitymi. Zaktualizowałem odpowiedź, aby była bardziej wyraźna i poprawna. Proszę poprawić mnie, jeśli nadal się mylę (lub samodzielnie zaktualizować odpowiedź). Po trzecie, usunąłem część dotyczącą zaakceptowanej odpowiedzi. Nigdzie nie używa rzutów wskaźnika na liczbę całkowitą i działa poprawnie (o ile rozumiem).
Vilx
@KeithThompson: Pytanie można zmienić, aby dotyczyło tego, jak std::swapmożna zaimplementować bez użycia trzeciej zmiennej.
jxh
10

Głupie pytania zasługują na odpowiednie odpowiedzi:

void sw2ap(int& a, int& b) {
  register int temp = a; // !
  a = b;
  b = temp;
}

Jedyne dobre użycie registersłowa kluczowego.

MSalters
źródło
1
Czy obiekt zadeklarowany w rejestrze klasy pamięci nie jest „zmienną”? Nie jestem też przekonany, że jest to dobre wykorzystanie rejestru, ponieważ jeśli twój kompilator nie może już tego zoptymalizować, to po co w ogóle próbować, powinieneś albo zaakceptować wszelkie bzdury, które dostaniesz, albo sam napisać asemblację ;-) Ale tak jak mówisz, podejrzane pytanie zasługuje na podejrzaną odpowiedź.
Steve Jessop
1
Prawie wszystkie współczesne kompilatory ignorują klasę rejestrów, ponieważ mają znacznie lepsze wyobrażenie o tym, co jest często używane, niż ty.
dyrektor generalny
6
Zauważ, że ta odpowiedź jest przeznaczona dla wywiadów, a nie kompilatorów. Szczególnie wykorzystuje to fakt, że ankieterzy, którzy zadają tego rodzaju pytania, nie rozumieją C ++. Dlatego nie mogą odrzucić tej odpowiedzi. (i nie ma standardowej odpowiedzi; ISO C ++ mówi o obiektach, a nie o zmiennych).
MSalters
Ach, rozumiem. Szukałem w standardzie C wzmianki o zmiennej jako rzeczowniku, a nie tylko o znaczeniu non-const. Znalazłem jeden w n1124, w sekcji o pętlach for definiujących zakres "zmiennych" zadeklarowanych w inicjatorze. Potem zdałem sobie sprawę, że pytanie brzmiało C ++ i nie zawracałem sobie głowy szukaniem, aby sprawdzić, czy C ++ gdziekolwiek popełnił tę samą literówkę.
Steve Jessop
3

Zamiana dwóch liczb za pomocą trzeciej zmiennej wygląda tak:

int temp;
int a=10;
int b=20;
temp = a;
a = b;
b = temp;
printf ("Value of a", %a);
printf ("Value of b", %b);

Zamiana dwóch liczb bez użycia trzeciej zmiennej

int a = 10;
int b = 20;
a = a+b;
b = a-b;
a = a-b;
printf ("value of a=", %a);
printf ("value of b=", %b);
Ashwini
źródło
2
#include<iostream.h>
#include<conio.h>
void main()
{
int a,b;
clrscr();
cout<<"\n==========Vikas==========";
cout<<"\n\nEnter the two no=:";
cin>>a>>b;
cout<<"\na"<<a<<"\nb"<<b;
a=a+b;
b=a-b;
a=a-b;

cout<<"\n\na="<<a<<"\nb="<<b;
getch();
}
vikas
źródło
1
Po tym, cout << "Enter the two no=:"jak spodziewałem się przeczytaćcout << "Now enter the two no in reverse order:"
user253751
Podobnie jak w przypadku kilku innych odpowiedzi, ma to nieokreślone zachowanie, jeśli a+blub a-bprzepełnienie. Ponadto void main()jest nieprawidłowy i <conio.h>niestandardowy.
Keith Thompson,
2

Ponieważ oryginalne rozwiązanie to:

temp = x; y = x; x = temp;

Możesz zrobić z niego dwie wkładki, używając:

temp = x; y = y + temp -(x=y);

Następnie zrób z tego jedną wkładkę, używając:

x = x + y -(y=x);
user3258202
źródło
1
Niezdefiniowane zachowanie. yjest odczytywana i zapisywana w tym samym wyrażeniu bez interwencji punktu sekwencji.
Keith Thompson,
1

Jeśli zmienisz trochę pytanie, aby zadać pytanie o 2 rejestry asemblerowe zamiast zmiennych, możesz użyć xchgoperacji jako jednej opcji, a operacji na stosie jako innej.

rkellerm
źródło
1
O xchgjakiej operacji masz na myśli? Pytanie nie określało architektury procesora.
Keith Thompson,
1

Rozważmy a=10, b=15:

Korzystanie z dodawania i odejmowania

a = a + b //a=25
b = a - b //b=10
a = a - b //a=15

Dzielenie i mnożenie

a = a * b //a=150
b = a / b //b=10
a = a / b //a=15
Venkat
źródło
1
Ma niezdefiniowane zachowanie przy przepełnieniu.
Keith Thompson,
1
Ponadto w C ++ może mieć niepożądane zachowanie, gdy wartości nie są tego samego typu. Na przykład a=10i b=1.5.
Matthew Cole
1
#include <iostream>
using namespace std;
int main(void)
{   
 int a,b;
 cout<<"Enter a integer" <<endl;
 cin>>a;
 cout<<"\n Enter b integer"<<endl;
 cin>>b;

  a = a^b;
  b = a^b;
  a = a^b;

  cout<<" a= "<<a <<"   b="<<b<<endl;
  return 0;
}

Aktualizacja: W tym przypadku pobieramy dane wejściowe z dwóch liczb całkowitych od użytkownika. Następnie używamy bitowej operacji XOR , aby je zamienić.

Że mamy dwie liczby całkowite a=4i b=9, a następnie:

a=a^b --> 13=4^9 
b=a^b --> 4=13^9 
a=a^b --> 9=13^9
Naeem Ul Hassan
źródło
Dodaj krótkie wyjaśnienie do swojej odpowiedzi dla przyszłych odwiedzających.
Nikolay Mihaylov
1
W tym przypadku pobieramy od użytkownika dwie liczby całkowite. Następnie używamy bitowej operacji xor dwa zamień je. Powiedzmy, że mamy dwa intergery a = 4 i b = 9, a następnie a = a ^ b -> 13 = 4 ^ 9 b = a ^ b -> 4 = 13 ^ 9 a = a ^ b -> 9 = 13 ^ 9
Naeem Ul Hassan
1

Oto jeszcze jedno rozwiązanie, ale jedno ryzyko.

kod:

#include <iostream>
#include <conio.h>
void main()
{

int a =10 , b =45;
*(&a+1 ) = a;
a =b;
b =*(&a +1);
}

każda wartość w lokalizacji a + 1 zostanie nadpisana.

Śmiałek
źródło
2
To była użyteczna odpowiedź, być może wartość zniknęła.
Bibi Tahira
1
Kilka rodzajów niezdefiniowanych zachowań. *(&a+1)może być b. void main()jest nieważny. <conio.h>jest niestandardowy (i nawet go nie używasz).
Keith Thompson,
Kod został przetestowany z innymi parametrami wyjściowymi, które wykluczyły, jak wspomniałem, ryzyko. Ryzyko pominięcia pamięci… To jest… Ale to była pożyteczna zamiana dwóch zmiennych bez udziału trzeciej.
DareDevil
Najgorsze jest to, że może faktycznie pracują kilka razy, więc może nie od razu sobie sprawę, że jest całkowicie uszkodzony i nie będzie pod optymalizacji innych kompilatorów, itd
avl_sweden
1

Oczywiście odpowiedź C ++ powinna brzmieć std::swap.

Jednak nie ma również trzeciej zmiennej w następującej implementacji swap:

template <typename T>
void swap (T &a, T &b) {
    std::pair<T &, T &>(a, b) = std::make_pair(b, a);
}

Lub jako jednowierszowe:

std::make_pair(std::ref(a), std::ref(b)) = std::make_pair(b, a);
jxh
źródło
0
#include <stdio.h>

int main()
{
    int a, b;
    printf("Enter A :");
    scanf("%d",&a);
    printf("Enter B :");
    scanf("%d",&b);
    a ^= b;
    b ^= a;
    a ^= b;
    printf("\nValue of A=%d B=%d ",a,b);
    return 1;
}
Siddiqui
źródło
0

to jest poprawny algorytm wymiany XOR

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

musisz upewnić się, że lokalizacje pamięci są różne, a także, że rzeczywiste wartości są różne, ponieważ A XOR A = 0

Gianluca Ghettini
źródło
Nie musisz upewniać się, że wartości są różne.
user253751
0

Możesz to zrobić… w prosty sposób… w ramach jednej linii Logiki

#include <stdio.h>

int main()
{
    int a, b;
    printf("Enter A :");
    scanf("%d",&a);
    printf("Enter B :");
    scanf("%d",&b);
    int a = 1,b = 2;
    a=a^b^(b=a);
    printf("\nValue of A=%d B=%d ",a,b);

    return 1;
}

lub

#include <stdio.h>

int main()
{
    int a, b;
    printf("Enter A :");
    scanf("%d",&a);
    printf("Enter B :");
    scanf("%d",&b);
    int a = 1,b = 2;
    a=a+b-(b=a);
    printf("\nValue of A=%d B=%d ",a,b);

    return 1;
}
MOHAMMAD S HUSSAIN
źródło
1
Niezdefiniowane zachowanie. W obu wersjach bjest zarówno odczytywany, jak i modyfikowany w ramach tego samego wyrażenia, bez pośredniego punktu sekwencji.
Keith Thompson,
0
public void swapnumber(int a,int b){
    a = a+b-(b=a);
    System.out.println("a = "+a +" b= "+b);
}
Ashish Bhavsar
źródło
0

Najlepszą odpowiedzią byłoby użycie XOR i użycie go w jednej linii byłoby fajne.

    (x ^= y), (y ^= x), (x ^= y);

x, y to zmienne, a przecinek między nimi wprowadza punkty sekwencji, więc nie zależy od kompilatora. Twoje zdrowie!

Ankit Sharma
źródło
0

Zobaczmy prosty przykład w c, aby zamienić dwie liczby bez użycia trzeciej zmiennej.

program 1:

#include<stdio.h>
#include<conio.h>
main()
{
int a=10, b=20;
clrscr();
printf("Before swap a=%d b=%d",a,b);
a=a+b;//a=30 (10+20)
b=a-b;//b=10 (30-20)
a=a-b;//a=20 (30-10)
printf("\nAfter swap a=%d b=%d",a,b);
getch();
}

Wynik:

Przed zamianą a = 10 b = 20 Po zamianie a = 20 b = 10

Program 2: Używanie * i /

Zobaczmy inny przykład zamiany dwóch liczb za pomocą * i /.

#include<stdio.h>
#include<conio.h>
main()
{
int a=10, b=20;
clrscr();
printf("Before swap a=%d b=%d",a,b);
a=a*b;//a=200 (10*20)
b=a/b;//b=10 (200/20)
a=a/b;//a=20 (200/10)
printf("\nAfter swap a=%d b=%d",a,b);
getch();
}

Wynik:

Przed zamianą a = 10 b = 20 Po zamianie a = 20 b = 10

Program 3: Wykorzystanie bitowego operatora XOR:

Bitowy operator XOR może służyć do zamiany dwóch zmiennych. XOR dwóch liczb xiy zwraca liczbę, która ma wszystkie bity jako 1, gdziekolwiek bity x i y różnią się. Na przykład XOR z 10 (binarnie 1010) i 5 (binarnie 0101) to 1111, a XOR z 7 (0111) i 5 (0101) to (0010).

#include <stdio.h>
int main()
{
 int x = 10, y = 5;
 // Code to swap 'x' (1010) and 'y' (0101)
 x = x ^ y;  // x now becomes 15 (1111)
 y = x ^ y;  // y becomes 10 (1010)
 x = x ^ y;  // x becomes 5 (0101)
 printf("After Swapping: x = %d, y = %d", x, y);
 return 0;

Wynik:

Po zamianie: x = 5, y = 10

Program 4:

Nikt jeszcze nie zasugerował używania std :: swap.

std::swap(a, b);

Nie używam żadnych zmiennych tymczasowych iw zależności od typu aib implementacja może mieć specjalizację, która też nie. Implementację należy napisać, wiedząc, czy „sztuczka” jest odpowiednia, czy nie.

Problemy z powyższymi metodami:

1) Podejście oparte na mnożeniu i dzieleniu nie działa, jeśli jedna z liczb jest równa 0, ponieważ iloczyn staje się 0 niezależnie od drugiej liczby.

2) Oba rozwiązania arytmetyczne mogą powodować przepełnienie arytmetyczne. Jeśli x i y są zbyt duże, dodawanie i mnożenie może wyjść poza zakres liczb całkowitych.

3) Kiedy używamy wskaźników do zmiennej i dokonujemy zamiany funkcji, wszystkie powyższe metody zawodzą, gdy oba wskaźniki wskazują na tę samą zmienną. Zobaczmy, co się stanie w tym przypadku, jeśli oba wskazują na tę samą zmienną.

// Metoda oparta na bitowym XOR

x = x ^ x; // x becomes 0
x = x ^ x; // x remains 0
x = x ^ x; // x remains 0

// Metoda oparta na arytmetyce

x = x + x; // x becomes 2x
x = x  x; // x becomes 0
x = x  x; // x remains 0

Zobaczmy następujący program.

#include <stdio.h>
void swap(int *xp, int *yp)
{
    *xp = *xp ^ *yp;
    *yp = *xp ^ *yp;
    *xp = *xp ^ *yp;
}

int main()
{
  int x = 10;
  swap(&x, &x);
  printf("After swap(&x, &x): x = %d", x);
  return 0;
}

Wyjście :

Po zamianie (& x, & x): x = 0

Zamiana zmiennej może być potrzebna w wielu standardowych algorytmach. Na przykład zobacz tę implementację QuickSort, w której możemy zamienić zmienną ze sobą. Powyższego problemu można uniknąć, stawiając warunek przed zamianą.

#include <stdio.h>
void swap(int *xp, int *yp)
{
    if (xp == yp) // Check if the two addresses are same
      return;
    *xp = *xp + *yp;
    *yp = *xp - *yp;
    *xp = *xp - *yp;
}
int main()
{
  int x = 10;
  swap(&x, &x);
  printf("After swap(&x, &x): x = %d", x);
  return 0;
}

Wyjście :

Po zamianie (& x, & x): x = 10

shobhit2905
źródło
0

Może nie na temat, ale jeśli wiesz, co zamieniasz pojedynczą zmienną między dwiema różnymi wartościami, możesz być w stanie wykonać logikę tablicową. Za każdym razem, gdy ten wiersz kodu zostanie uruchomiony, zamieni on wartość między 1 a 2.

n = [2, 1][n - 1]
pytający
źródło
0

Mógłbyś:

std::tie(x, y) = std::make_pair(y, x);

Lub użyj make_tuple podczas zamiany więcej niż dwóch zmiennych:

std::tie(x, y, z) = std::make_tuple(y, z, x);

Ale nie jestem pewien, czy wewnętrznie std :: tie używa zmiennej tymczasowej, czy nie!

pooya13
źródło
0

W javascript:

function swapInPlace(obj) {
    obj.x ^= obj.y
    obj.y ^= obj.x
    obj.x ^= obj.y
}

function swap(obj) {
    let temp = obj.x
    obj.x = obj.y
    obj.y = temp
}

Zwróć uwagę na czas wykonania obu opcji.

Po uruchomieniu tego kodu zmierzyłem go.

console.time('swapInPlace')
swapInPlace({x:1, y:2})
console.timeEnd('swapInPlace') // swapInPlace: 0.056884765625ms

console.time('swap')
swap({x:3, y:6})
console.timeEnd('swap')        // swap: 0.01416015625ms

Jak widać (i jak wielu powiedziało), zamiana na miejscu (xor) zajmuje dużo czasu niż inna opcja wykorzystująca zmienną temp.

ofir_aghai
źródło
0

R brakuje równoczesnego przypisania, jak zaproponował Edsger W. Dijkstra w A Discipline of Programming , 1976, rozdz. 4, s. 29. Pozwoliłoby to na eleganckie rozwiązanie:

a, b    <- b, a         # swap
a, b, c <- c, a, b      # rotate right
user3604103
źródło
-1
a = a + b - (b=a);

To bardzo proste, ale może budzić ostrzeżenie.

alsimoneau
źródło
5
Ostrzeżenie jest takie, że to nie działa? Nie wiemy, czy b=ajest wykonywana przed, czy po a + b.
Bo Persson,
-1

jednoliniowe rozwiązanie do zamiany dwóch wartości w języku c.

a=(b=(a=a+b,a-b),a-b);
HARITUSH
źródło
Niezdefiniowane zachowanie przy przepełnieniu.
Keith Thompson,
-1
second_value -= first_value;
first_value +=  second_value;
second_value -= first_value;
second_value *= -1;
Zikria Qureshi
źródło
Ma to niezdefiniowane zachowanie, jeśli którakolwiek z operacji przepełnia lub niedomiar. Może również stracić precyzję, jeśli obiekty są zmiennoprzecinkowe.
Keith Thompson,