インタビューで尋ねられた非常にトリッキーな質問の1つ。
a=10およびのような2つの変数の値を交換しますb=15。
通常、2つの変数の値を交換するには、次のような3番目の変数が必要です。
temp=a;
a=b;
b=temp;
ここでの要件は、3番目の変数を使用せずに2つの変数の値を交換することです。
インタビューで尋ねられた非常にトリッキーな質問の1つ。
a=10およびのような2つの変数の値を交換しますb=15。
通常、2つの変数の値を交換するには、次のような3番目の変数が必要です。
temp=a;
a=b;
b=temp;
ここでの要件は、3番目の変数を使用せずに2つの変数の値を交換することです。
回答:
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
一般的な形式は次のとおりです。
A = A operation B
B = A inverse-operation B
A = A inverse-operation B
ただし、オーバーフローに注意する必要があり、すべての操作が、その操作が定義されているすべての値に対して適切に定義されている逆を持っているわけではありません。例:*および/またはAまたはBが0になるまで機能します
xorは、すべてのintに対して定義され、それ自体が逆であるため、特に楽しいです
a = a + b
b = a - b // b = a
a = a - b
a+bオーバーフローした場合はどうなりますか?
int、unsigned short+ Bオーバーフローするので、...)、それはまだ、でもオーバーフローして、最後に出て働き、そして- bがアンダーフローします。これらの基本的な整数型では、値はロールオーバーされます。
unsigned整数型でこれを使用しても問題はなく、常に機能します。符号付きの型では、オーバーフロー時に未定義の動作を呼び出します。
std::swapまだ誰もの使用を提案していません。
std::swap(a, b);
私は一時的な変数を使用していません。aまたb、タイプや実装によっては、スペシャライゼーションも使用しない場合があります。実装は、「トリック」が適切かどうかを知って書かれるべきです。2番目の推測を試みても意味がありません。
より一般的には、ADLが可能であればより良いオーバーロードを見つけることができるクラス型で機能するため、おそらくこのようなことをしたいと思うでしょう。
using std::swap;
swap(a, b);
もちろん、この回答に対するインタビュアーの反応は、空席について多くのことを語るかもしれません。
std::、ユーザーswap定義型のユーザー定義実装を呼び出すこともできます(これは、「ADLが可能な場合により良いオーバーロードを見つけることができるクラス型で機能する」という意味です)。
std::swapその実装で追加の一時メモリを使用しますか?cplusplus.com/reference/utility/swap
manuがすでに述べたように、XORアルゴリズムはすべての整数値に対して機能する人気の高いアルゴリズムです(その場合、運が良ければキャスティングされたポインターも含まれます)。完全を期すために、足し算と引き算を使った、それほど強力ではない別のアルゴリズムについて触れておきます。
A = A + B
B = A - B
A = A - B
ここでは、オーバーフロー/アンダーフローに注意する必要がありますが、それ以外は同じように機能します。XORが許可されていない場合は、floats / doublesでこれを試すこともできます。
std::swap()でしょうか-または?確かに、ポインターを整数に変換したり、元に戻したりすることはできます(保証されていない十分な大きさの整数型があると想定しています)が、それはあなたが提案したものではありません。ポインタは整数であり、整数に変換できるわけではないことを暗示しました。はい、受け入れられた答えはポインタに対しては機能しません。チャールズ・ベイリーの答え使用してはstd::swap本当に唯一の正しいものです。
std::swap、3番目の変数を使用せずにどのように実装できるかについて修正することができます。
愚かな質問は適切な答えに値します:
void sw2ap(int& a, int& b) {
register int temp = a; // !
a = b;
b = temp;
}
registerキーワードの唯一の有効利用。
#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();
}
cout << "Enter the two no=:"読むcout << "Now enter the two no in reverse order:"
a+bまたはa-bオーバーフローした場合の動作は未定義です。また、void main()無効で<conio.h>あり、非標準です。
元の解決策は次のとおりです:
temp = x; y = x; x = temp;
あなたはそれを2つのライナーにすることができます:
temp = x; y = y + temp -(x=y);
次に、次を使用して1つのライナーにします。
x = x + y -(y=x);
yシーケンスポイントが介在しない同じ式で読み書きされます。
#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=4とb=9します。
a=a^b --> 13=4^9
b=a^b --> 4=13^9
a=a^b --> 9=13^9
これがもう1つの解決策ですが、1つのリスクがあります。
コード:
#include <iostream>
#include <conio.h>
void main()
{
int a =10 , b =45;
*(&a+1 ) = a;
a =b;
b =*(&a +1);
}
ロケーションa + 1の値はすべてオーバーライドされます。
*(&a+1)たぶんb。void main()無効です。<conio.h>標準ではありません(使用していません)。
これが正しい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であるため、メモリの場所が異なり、実際の値も異なることを確認する必要があります。
あなたはそうすることができます...簡単な方法で...一行以内ロジック
#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;
}
bシーケンスポイントが介在することなく、同じ式内で読み取りと変更の両方が行われます。
最良の答えはXORを使用することであり、1行で使用するのがクールです。
(x ^= y), (y ^= x), (x ^= y);
x、yは変数であり、それらの間のコンマはシーケンスポイントを導入するため、コンパイラに依存しません。乾杯!
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
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変数を使用する他のオプションよりもかなり時間がかかります。
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