3番目の変数を使用せずに2つの変数値を交換する


104

インタビューで尋ねられた非常にトリッキーな質問の1つ。

a=10およびのような2つの変数の値を交換しますb=15

通常、2つの変数の値を交換するには、次のような3番目の変数が必要です。

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

ここでの要件は、3番目の変数を使用せずに2つの変数の値を交換することです。


76
ワオ。不適切に選ばれたインタビューの質問私見。これは、実際に役立つことはめったにないにしても、テクニックです。コンパイラのオプティマイザを混乱させるだけの可能性があり、「一時的なスワップ」よりもコードの効率が低下します。あなたがインタビューしているこの場所が非常に数学が重いものに関与しない限り(暗号化アルゴリズムの開発などを考えてください)、そのような質問をする正当な理由は想像できません。
ダン成形

10
確かに、その質問は、候補者が本番用コードではほとんど役に立たないこの特定のトリックを知っているかどうか以外には何も教えません。オンザフライでそれを見つけ出す臨時のウィザードに出くわすかもしれませんが、トリックをまだ知らないそのような人々はかなり珍しいでしょう。
ceo、

2
たぶん彼らは、そのようなトリックを知っていることが優れたプログラマーになる理由だと考える人々を排除したいのでしょうか?また、xor-trickについて読むときは、いつ失敗するかに注意してください(これにより、IMOは汎用の整数スワッピングにはほとんど役に立ちません)。
UncleBens 2009

回答:


155

使用XOR交換アルゴリズムを

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


なぜテストなのか?

テストでは、xとyが異なる値ではなく異なるメモリ位置にあることを確認します。これは(p xor p) = 0、xとyの両方が同じメモリ位置を共有している場合、一方が0に設定されると、両方とも0に設定されます。 0(同じであるため)、つまり、関数は* xと* yの両方を0に設定します。

それらが同じ値を持っているが同じメモリ位置を持たない場合、すべてが期待どおりに機能します

*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


これを使用する必要がありますか?

一般的なケースでは、ありません。コンパイラーは一時変数を最適化し、スワップが一般的な手順であることを考えると、プラットフォームに最適なマシンコードを出力する必要があります。

たとえば、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;    
}

以下を使用してコンパイル:

gcc -Os main.c -o swap

XORバージョンは

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

ここで、一時変数を含むバージョンは次のようになります。

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

1
テストの理由を説明する価値があるかもしれません(つまり、xとyが同じオブジェクトを参照する場合、トリプルxorアプローチはひどく失敗します)。
dmckee ---元モデレーターの子猫

1
コードが壊れているときに、どうしてこれほど多くの賛成票を得たのかはわかりません。テストしたコードセグメントの両方のスワップは完全に正しくありません。よく見てください。
SoapBox 2010

@SoapBox:非常に良い点です!XORスワップはxをゼロにして&yをそのままにし、一時的なスワップはxの値のままにします。おっと!
ドリューホール

4
@SoapBox良いキャッチ。修正して再テストしました。タイミングに大きな違いはありません。
Yacoby

4
質問者はintではなく2つの変数を言った。:P
Plumenator

93

一般的な形式は次のとおりです。

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

ただし、オーバーフローに注意する必要があり、すべての操作が、その操作が定義されているすべての値に対して適切に定義されている逆を持っているわけではありません。例:*および/またはAまたはBが0になるまで機能します

xorは、すべてのintに対して定義され、それ自体が逆であるため、特に楽しいです


XOR(X、X)== 0なので、xorは、入れ替えられる2つの値が等しい場合に、intを除いて機能します。これを指摘するのは私が最初ではなく、あまりにも多くの人が(ここや他の場所で)誰もが信用できると言っているとは言っていません。
AlanK

85
a = a + b
b = a - b // b = a
a = a - b

16
a+bオーバーフローした場合はどうなりますか?
Alok Singhal、2010年

2
@Alok:これは考慮に入れられていないため、実用的ではありません:)
Dor

4
aとbが同じ、基本的な、サイズの整数型(のようなのであればintunsigned short+ Bオーバーフローするので、...)、それはまだ、でもオーバーフローして、最後に出て働き、そして- bがアンダーフローします。これらの基本的な整数型では、値はロールオーバーされます。
Patrick Johnmeyer

10
Cでは、unsigned整数型でこれを使用しても問題はなく、常に機能します。符号付きの型では、オーバーフロー時に未定義の動作を呼び出します。
jpalecek 2010

1
@Shahbaz:符号付き整数が2の補数で格納されている場合でも、オーバーフロー時の動作は未定義です。
キーストンプソン、

79

std::swapまだ誰もの使用を提案していません。

std::swap(a, b);

は一時的な変数を使用していません。aまたb、タイプや実装によっては、スペシャライゼーションも使用しない場合があります。実装は、「トリック」が適切かどうかを知って書かれるべきです。2番目の推測を試みても意味がありません。

より一般的には、ADLが可能であればより良いオーバーロードを見つけることができるクラス型で機能するため、おそらくこのようなことをしたいと思うでしょう。

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

もちろん、この回答に対するインタビュアーの反応は、空席について多くのことを語るかもしれません。


もちろん、C ++ 0xのswapは右辺値参照を使用するため、さらに改善されます!
jk。

16
誰もが光沢のあるxorや他のトリックを披露したいと思っていますが、その使い方についてはさまざまな注意事項がありますが、これが間違いなく最良の答えです。

2
std :: swap(a、b);でない理由 ?なぜ使用するのですか?
Dinaiz 2016

2
@Dinaizなしでstd::、ユーザーswap定義型のユーザー定義実装を呼び出すこともできます(これは、「ADLが可能な場合により良いオーバーロードを見つけることができるクラス型で機能する」という意味です)。
カイルストランド

std::swapその実装で追加の一時メモリを使用しますか?cplusplus.com/reference/utility/swap
忍者

19

manuがすでに述べたように、XORアルゴリズムはすべての整数値に対して機能する人気の高いアルゴリズムです(その場合、運が良ければキャスティングされたポインターも含まれます)。完全を期すために、足し算と引き算を使った、それほど強力ではない別のアルゴリズムについて触れておきます。

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

ここでは、オーバーフロー/アンダーフローに注意する必要がありますが、それ以外は同じように機能します。XORが許可されていない場合は、floats / doublesでこれを試すこともできます。


すべての整数値(ポインタを含む)」-何ですか?いいえ、ポインタは整数ではなく、ポインタ値をXORすることはできません。また、浮動小数点値をxorすることもできません。ただし、加算/減算方式は、オーバーフローまたはアンダーフローで(符号付きまたは浮動小数点型の)未定義の動作を行い、浮動小数点の精度を失う可能性があり、ポインターやその他の非数値型には適用できません。
キーストンプソン

@KeithThompson-OK、浮動小数点の精度が失われることは事実ですが、状況によっては許容できる場合もあります。ポインターについては、まあ、ポインターを整数に自由にキャストして再度戻すことができないコンパイラー/プラットフォームについては聞いたことがありません(もちろん、適切なサイズの整数を選択するように注意してください)。しかし、私はC / C ++の専門家ではありません。私はそれが標準に反することを知っていますが、この動作はすべてに渡ってかなり一貫しているという印象を受けました。
Vilx- 2016

1
@ Vilx-:一時変数を使用して精度を回避することが非常に簡単なのに、なぜ精度の低下が許容できるのstd::swap()でしょうか-または?確かに、ポインターを整数に変換したり、元に戻したりすることはできます(保証されていない十分な大きさの整数型があると想定しています)が、それはあなたが提案したものではありません。ポインタ整数であり、整数に変換できるわけではないことを暗示しました。はい、受け入れられた答えはポインタに対しては機能しません。チャールズ・ベイリーの答え使用してはstd::swap本当に唯一の正しいものです。
キーストンプソン

1
まず、質問は「第3変数を使わずに入れ替える方法」で、その理由は「面接質問」でした。私はもう1つの可能な答えを与えた。第二に、ポインタが整数であることをほのめかして申し訳ありません。答えを更新して、うまくいけばより明確で正確になるようにした。それでも問題がある場合は修正してください(または自分で回答を更新してください)。第三に、受け入れた回答の部分を削除しました。整数へのポインターキャストを使用しておらず、正しく機能します(私が理解している限り)。
Vilx-

@KeithThompson:質問はstd::swap、3番目の変数を使用せずにどのように実装できるかについて修正することができます。
-jxh

10

愚かな質問は適切な答えに値します:

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

registerキーワードの唯一の有効利用。


1
ストレージクラスレジスタで宣言されたオブジェクトは「変数」ではありませんか?また、これがレジスターの良い使用法であるとは確信していません。コンパイラーがこれをすでに最適化できない場合は、試みても意味がないので、取得したゴミを受け入れるか、自分でアセンブリを作成する必要があります;-)しかし、あなたは、危険な質問は危険な答えに値します。
スティーブジェソップ

1
最近のコンパイラーのほとんどは、レジスター・ストレージ・クラスを無視します。なぜなら、頻繁にアクセスされるものについて、ユーザーよりもはるかによく理解しているからです。
CEO、

6
この回答は、コンパイラではなくインタビューを対象としています。特に、この種の質問をするようなインタビュアーはC ++を本当に理解していないという事実を利用しています。したがって、彼らはこの答えを拒否することはできません。(そして標準的な答えはありません。ISOC ++は変数ではなくオブジェクトについて話します)。
MSalters 2009

ああ、わかった。私はC規格で変数を名詞として言及するために探していました。イニシャライザで宣言された「変数」のスコープを定義するforループのセクションで、n1124に1つ見つかりました。その後、私は問題がC ++であることに気付き、C ++がどこでも同じタイプミスをしたかどうかを調べるために検索する手間を省きました。
スティーブジェソップ

3

3番目の変数を使用して2つの数値を交換すると、次のようになります。

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

3番目の変数を使用せずに2つの数値を交換する

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);

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();
}

1
cout << "Enter the two no=:"読むcout << "Now enter the two no in reverse order:"
つもり

他のいくつかの回答と同様に、a+bまたはa-bオーバーフローした場合の動作は未定義です。また、void main()無効で<conio.h>あり、非標準です。
キーストンプソン

2

元の解決策は次のとおりです:

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

あなたはそれを2つのライナーにすることができます:

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

次に、次を使用して1つのライナーにします。

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

1
未定義の動作。yシーケンスポイントが介在しない同じ式で読み書きされます。
キーストンプソン

1

変数の代わりに2つのアセンブリレジスタについて質問する質問を少し変更するxchgと、1つのオプションとして操作を使用し、別のオプションとしてスタック操作を使用できます。


1
どのようなxchg操作を参照していますか?質問では、CPUアーキテクチャは指定されていません。
キース・トンプソン

1

考えてみましょうa=10b=15

加算と減算の使用

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

除算と乗算の使用

a = a * b //a=150
b = a / b //b=10
a = a / b //a=15

1
オーバーフローに対する未定義の動作があります。
キーストンプソン

1
また、C ++では、値が同じ型でない場合に望ましくない動作をする可能性があります。たとえば、a=10およびb=1.5
マシューコール

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;
}

更新:この例では、ユーザーから2つの整数を入力しています。次に、ビットごとのXOR演算を使用してそれらを交換します。

2つの整数があるa=4b=9します。

a=a^b --> 13=4^9 
b=a^b --> 4=13^9 
a=a^b --> 9=13^9

今後の訪問者のために、回答に簡単な説明を追加してください。
Nikolay Mihaylov

1
これでは、ユーザーから2つの整数を入力しています。次に、ビット単位のxor演算を使用して2つスワップします。2つの整数a = 4とb = 9があるとすると、a = a ^ b-> 13 = 4 ^ 9 b = a ^ b-> 4 = 13 ^ 9 a = a ^ b-> 9 = 13 ^ 9
Naeem Ul Hassan

1

これがもう1つの解決策ですが、1つのリスクがあります。

コード:

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

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

ロケーションa + 1の値はすべてオーバーライドされます。


2
それは有用な答えでした、おそらく価値は消えました。
Bibi Tahira 2013

1
いくつかの種類の未定義の動作。*(&a+1)たぶんbvoid main()無効です。<conio.h>標準ではありません(使用していません)。
キーストンプソン

コードは他の出力パラメーターを使用してテストされましたが、私がリスクについて述べたように、これらは除外されました。オーバーライドされたメモリのリスク..それはそこにあります。しかし、それは2番目の変数を3番目の変数の関与なしに交換するのに役立ちました。
DareDevil

最悪の部分は、実際に機能する場合があるため、完全に壊れており、最適化や他のコンパイラーなどで失敗することにすぐには気付かない可能性がある
avl_sweden

1

もちろん、C ++の答えはになりますstd::swap

ただし、以下のの実装には3番目の変数もありませんswap

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

または、ワンライナーとして:

std::make_pair(std::ref(a), std::ref(b)) = std::make_pair(b, a);

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;
}

0

これが正しい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;
      }
   }
}

A XOR A = 0であるため、メモリの場所が異なり、実際の値も異なることを確認する必要があります。


値が異なることを確認する必要はありません。
user253751

0

あなたはそうすることができます...簡単な方法で...一行以内ロジック

#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;
}

または

#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;
}

1
未定義の動作。どちらのバージョンでも、bシーケンスポイントが介在することなく、同じ式内で読み取りと変更の両方が行われます。
キーストンプソン


0

最良の答えはXORを使用することであり、1行で使用するのがクールです。

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

x、yは変数であり、それらの間のコンマはシーケンスポイントを導入するため、コンパイラに依存しません。乾杯!


0

3番目の変数を使用せずに2つの数値を交換する簡単なcの例を見てみましょう。

プログラム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();
}

出力:

交換前a = 10 b = 20交換後a = 20 b = 10

プログラム2:*と/の使用

*と/を使用して2つの数値を交換する別の例を見てみましょう。

#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();
}

出力:

交換前a = 10 b = 20交換後a = 20 b = 10

プログラム3:ビットごとのXOR演算子を使用する:

ビットごとのXOR演算子を使用して、2つの変数を交換できます。2つの数値xとyのXORは、xとyのビットが異なるすべてのビットが1である数値を返します。たとえば、10(バイナリ1010の場合)と5(バイナリ0101の場合)のXORは1111で、7(0111)と5(0101)のXORは(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;

出力:

交換後:x = 5、y = 10

プログラム4:

まだ誰もstd :: swapの使用を提案していません。

std::swap(a, b);

私は一時変数を使用していません。aとbのタイプによっては、実装が特殊化されていない場合もあります。実装は、「トリック」が適切かどうかを知って書かれるべきです。

上記の方法の問題:

1)乗算と除算に基づくアプローチは、一方の数値が0の場合、もう一方の数値に関係なく積が0になるため機能しません。

2)両方の算術ソリューションは算術オーバーフローを引き起こす可能性があります。xとyが大きすぎると、加算と乗算が整数の範囲から外れる場合があります。

3)変数へのポインターを使用して関数をスワップする場合、両方のポインターが同じ変数を指すと、上記のすべてのメソッドが失敗します。この場合、両方が同じ変数を指している場合に何が起こるかを見てみましょう。

//ビットごとのXORベースのメソッド

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

//算術ベースのメソッド

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

次のプログラムを見てみましょう。

#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;
}

出力

swap(&x、&x)の後:x = 0

変数をそれ自体と交換することは、多くの標準アルゴリズムで必要になる場合があります。たとえば、QuickSortのこの実装を参照してください。この実装では、変数をそれ自体と交換できます。上記の問題は、スワッピングの前に条件を置くことで回避できます。

#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;
}

出力

スワップ後(&x、&x):x = 10


0

トピックから外れているかもしれませんが、1つの変数を2つの異なる値の間で交換していることがわかっている場合は、配列ロジックを実行できる可能性があります。このコード行が実行されるたびに、値が1と2の間で交換されます。

n = [2, 1][n - 1]

0

あなたがすることができます:

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

または、3つ以上の変数をスワップするときにmake_tupleを使用します。

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

しかし、内部的にstd :: tieが一時変数を使用するかどうかはわかりません!


0

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
}

両方のオプションの実行時間に注意してください。

このコードを実行することで、測定しました。

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

ご覧のとおり(および多くの人が言ったように)、所定の場所でスワップ(xor)を行うと、temp変数を使用する他のオプションよりもかなり時間がかかります。


0

Rには、Essger W. DijkstraがA Discipline of Programming、1976、ch.4、p.29で提案した同時割り当てがありません。これにより、エレガントなソリューションが可能になります。

a, b    <- b, a         # swap
a, b, c <- c, a, b      # rotate right

-1
a = a + b - (b=a);

とてもシンプルですが、警告が出るかもしれません。


5
警告は機能しないということですか?のb=a前か後かはわかりませんa + b
Bo Persson


-1
second_value -= first_value;
first_value +=  second_value;
second_value -= first_value;
second_value *= -1;

いずれかの演算がオーバーフローまたはアンダーフローした場合の動作は未定義です。オブジェクトが浮動小数点の場合も、精度が失われる可能性があります。
キーストンプソン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.