私はC#または一般的なバイナリ演算子、特に ^-排他的OR を理解しようとしています。
例えば:
正の整数の配列を指定します。奇数回発生する1つの数値を除き、すべての数値は偶数回発生します。 O(n)時間と定数空間で数値を見つけます。
これは、次のように^を使用して実行できます。すべての要素のビット単位XORを実行します。
どのように機能しますか?
私がする時:
int res = 2 ^ 3;
res = 1;
int res = 2 ^ 5;
res = 7;
int res = 2 ^ 10;
res = 8;
実際に何が起こっていますか?他のビットマジックとは何ですか?参照して詳細を確認できる参考資料はありますか?
それがどのように機能するかを見るためには、最初に両方のオペランドをバイナリで書く必要があります。ビットごとの操作は個々のビットで機能するからです。
次に、特定の演算子に 真理値表 を適用できます。 2つのオペランドの同じ位置(同じ場所の値)を持つビットの各ペアに作用します。したがって、A
の左端ビット(MSB)がB
のMSBと結合され、結果のMSBが生成されます。
例:2^10
:
0010 2
XOR 1010 8 + 2
----
1 xor(0, 1)
0 xor(0, 0)
0 xor(1, 1)
0 xor(0, 0)
----
= 1000 8
結果は8です。
私はこれがかなり古い投稿であることを知っていますが、何か他のものを探している間につまずいたので、答えを簡単にしたかったです。
XOR(排他的OR /いずれかまたは)、オン/オフの切り替えとして簡単に変換できます。
指定したビットを除外するか含めるか。
4ビット(1111)を使用すると、0〜15で16の可能な結果が得られます。
decimal | binary | expanded
0 | 0000 |
1 | 0001 |
2 | 0010 |
3 | 0011 | (1+2)
4 | 0100 |
5 | 0101 | (1+4)
6 | 0110 | (2+4)
7 | 0111 | (1+2+4)
8 | 1000 |
9 | 1001 | (1+8)
10 | 1010 | (2+8)
11 | 1011 | (1+2+8)
12 | 1100 | (4+8)
13 | 1101 | (1+4+8)
14 | 1110 | (2+4+8)
15 | 1111 | (1+2+4+8)
バイナリ値の左側の10進数値は、XORおよびその他のビット単位の演算で使用される数値です。
たとえば、0011
はビット1と2がオンで、ビット4と8はオフのままです。オンになっているビットを示す3
の10進値として表され、1+2
として展開された形式で表示されます。
XORの背後にあるロジックで何が起こっているかについては、ここにいくつかの例があります
元の投稿から
2 ^ = 1
- 2は1 + 2(3)remove 2 = 1のメンバーです
2 ^ 5 = 7
- 2は1 + 4(5)add 2 = 1 + 2 + 4(7)のメンバーではありません
2 ^ 1 = 8
- 2は2 + 8(10)remove 2 = 8のメンバーです
さらなる例
1 ^ = 2
- 1は1 + 2(3)remove 1 = 2のメンバーです
4 ^ 5 = 1
- 4は1 + 4(5)remove 4 = 1のメンバーです
4 ^ 4 = 0
- 4はそれ自体のメンバーですremove 4 =
1 ^ 2 ^ = 0
ロジック:((1 ^ 2)^(1 + 2))
- (1 ^ 2)1は2のメンバーではないadd 2 = 1 + 2(3)
- (3 ^ 3)1と2は1 + 2(3)のメンバーです1 + 2 (3)= 0
1 ^ 1 ^ 0 ^ 1 = 1
論理:(((1 ^ 1)^ 0)^ 1)
- (1 ^ 1)1は1 remove 1 = 0のメンバーです
- (0 ^ 0)0は0のメンバーですremove 0 = 0
- (0 ^ 1)0は1 add 1 = 1のメンバーではありません
1 ^ 8 ^ 4 = 13
ロジック:((1 ^ 8)^ 4)
- (1 ^ 8)1は8のメンバーではないadd 1 = 1 + 8(9)
- (9 ^ 4)1と8は4 addのメンバーではない1 + 8 = 1 + 4 + 8(13)
4 ^ 13 ^ 1 = 3
ロジック:((4 ^(1 + 4 + 8))^(2 + 8))
- (4 ^ 13)4は1 + 4 + 8(13)のメンバー4 = 1 + 8(9)
- (9 ^ 10)8は2 + 8(10)remove 8 = 2 のメンバーです
- 1は2のメンバーではありません
+8(10)add 1 = 1 + 2(3)4 ^ 10 ^ 1 = 3
ロジック:((4 ^(2 + 8))^(1 + 4 + 8))
- (4 ^ 10)4は2 + 8(10)add 4 = 2 + 4 + 8(14)
- (14 ^ 13)4と8は1 + 4 + 8(13)のメンバーです4 + 8 =- 1
- 2は1のメンバーではありません
+ 4 + 8(13)add 2 = 1 + 2(3)
これを示すもう1つの方法は、XORの代数を使用することです。個々のビットについて何も知る必要はありません。
任意の数値x、y、zの場合:
XORは可換です:_x ^ y == y ^ x
_
XORは結合的です:x ^ (y ^ z) == (x ^ y) ^ z
IDは0です:_x ^ 0 == x
_
すべての要素はそれ自身の逆です:_x ^ x == 0
_
これを考えると、記載されている結果を証明するのは簡単です。シーケンスを考えます:
_a ^ b ^ c ^ d ...
_
XORは可換および結合であるため、順序は関係ありません。要素をソートします。
これで、隣接する同一の要素_x ^ x
_を_0
_(自己反転プロパティ)に置き換えることができます。また、_0
_は削除できます(IDであるため)。
できるだけ長く繰り返します。偶数回出現する数字には、整数個のペアがあるため、それらはすべて0になって消えます。
最終的に、1つの要素だけが残ります。これは、奇数回出現する要素です。 2回表示されるたびに、これら2つは消えます。最終的には、1つのオカレンスが残ります。
[更新]
この証明は、操作に関する特定の仮定のみを必要とすることに注意してください。具体的には、演算子_.
_を持つセットSに次のプロパティがあると仮定します。
連想性:Sのx
、y
、およびz
のx . (y . z) = (x . y) . z
.
アイデンティティ:単一の要素e
が存在し、Sのすべてのx
に対して_e . x = x . e = x
_が存在します。
クロージャー:Sのx
およびy
については、_x . y
_もSにあります。
自己反転:Sのx
の場合、_x . x = e
_
結局のところ、可換性を仮定する必要はありません。私たちはそれを証明することができます:
_(x . y) . (x . y) = e (by self-inverse)
x . (y . x) . y = e (by associativity)
x . x . (y . x) . y . y = x . e . y (multiply both sides by x on the left and y on the right)
y . x = x . y (because x . x = y . y = e and the e's go away)
_
さて、私は「個々のビットについて何も知る必要はない」と言った。これらの特性を満たすグループであれば十分であり、そのようなグループは必ずしもXORの下で整数と同型である必要はないと考えていました。
しかし、@ Steve Jessupはコメントで間違っていることを証明しました。 {0,1}によるスカラー乗算を次のように定義する場合:
_0 * x = 0
1 * x = x
_
...この構造は、すべての ベクトル空間の軸 整数mod 2を満たします。
したがって、そのような構造は、成分ごとのXORの下でビットのベクトルのセットと同型です。
ビット単位演算子は、整数値内のビットをビットの小さな配列として扱います。これらの各ビットはtiny bool
valueのようなものです。ビット単位の排他的or演算子を使用する場合、演算子の動作の1つの解釈は次のとおりです。
最終的な効果は、1ビットがfalse
で始まり、「トグル」の総数が偶数である場合、最後にまだfalse
になることです。 「トグル」の総数が奇数の場合、最後にtrue
になります。
「ブール値の小さな配列」と考えるだけで、意味を持ち始めます。
これは、XOR自身の結果がゼロになる数の単純な事実に基づいています。
およびXOR 0の数の結果はその数自体になります。
したがって、配列= {5,8,12,5,12}がある場合。
5は2回発生しています。 8は1回発生しています。 12が2回発生しています。
奇数回発生する数を見つけなければなりません。明らかに、8が数字です。
Res = 0およびXORで配列のすべての要素を使用します。
int res=0; for(int i:array) res = res ^ i;
1st Iteration: res = 0^5 = 5
2nd Iteration: res = 5^8
3rd Iteration: res = 5^8^12
4th Iteration: res = 5^8^12^5 = 0^8^12 = 8^12
5th Iteration: res = 8^12^12 = 8^0 = 8
ビット上のXOR(排他的OR)演算子の定義は次のとおりです。
0 XOR 0 = 0
0 XOR 1 = 1
1 XOR 0 = 1
1 XOR 1 = 0
想像する方法の1つは、右側の「1」が左側からビットを変更し、右側の0が左側のビットを変更しないと言うことです。ただし、XORは可換であるため、辺が逆の場合も同じです。任意の数値をバイナリ形式で表現できるため、任意の2つの数値をXOR演算できます。
可換であることを証明するには、単にその定義を見て、両側のビットのすべての組み合わせについて、両側が変更された場合に結果が同じになることを確認できます。連想性があることを証明するには、3ビットを相互にXORするという可能な組み合わせをすべて実行するだけでよく、結果は順序に関係なく同じままです。
さて、上記を証明したように、XOR=それ自体で同じ数。操作は個々のビットで機能するので、2つの数でテストできます:0と1 。
0 XOR 0 = 0
1 XOR 1 = 0
したがって、XOR自体に数値を入力すると、常に0になります(信じるかどうかは異なりますが、そのプロパティXORは、 0をCPUレジスタにロードする必要があります。明示的に0をレジスタにプッシュするよりも、ビット演算を実行する方が高速です。コンパイラは、アセンブリコードをXORそれ自体にレジスタ)に生成します) 。
ここで、X XOR Xが0であり、XORが連想的であり、一連の数字で繰り返されていない数字を見つける必要がある場合、他のすべての数字は2回(または他の奇数回)繰り返されています。繰り返し数を一緒にした場合、XOR to0。0とXORされたものはすべて残ります。そのため、このようなシーケンスをXOR演算すると、繰り返されない(または偶数回繰り返される)数字が残ることになります。
This には、ビットをいじることによって行われるさまざまな機能のサンプルがたくさんあります。一部は非常に複雑になる可能性があるので注意してください。
ビット操作を理解するために必要なことは、少なくとも次のとおりです。
XORの場合、真理値表は簡単です。
_1^1 = 0
1^0 = 1
0^1 = 1
0^0 = 0
_
結果のビットn
を取得するには、最初と2番目の入力のビットn
にルールを適用します。
_1^1^0^1
_または他の組み合わせを計算しようとすると、奇数の1があれば結果は1であり、そうでなければ0であることがわかります。また、それ自体とXORされた数値は0であり、計算を実行する順序は関係ありません。 1^1^(0^1) = 1^(1^0)^1
。
つまり、XOR=リスト内のすべての数値、重複している(または偶数回存在する)数値は、XOR to 0および奇数回存在するものだけが残ります。