はじめに
複数のデータを保存したい!でも保存先のカラムは1つしかない!
そんな時、役立ったビット演算について自分用にまとめる。
シーン例
フォームのチェックボックスから複数の値を受け取りたい。
各選択肢用のカラムがある場合は、それぞれ格納すればよいがもし1つしかカラムがない場合はどうするのか...?
データをカンマ区切りで連結して、保存すればいいのかな?と思っていたがビット演算なるものがあるからそれを使うとよいとアドバイスを頂いた。
ビット演算とは
ビット演算とは、主にコンピュータ上で行われる演算の一つで、対象データをビット列(2進数の0と1の羅列)とみなして、ビットの移動やビット単位の論理演算などを行うもの。
引用元:ビット演算とは
使ってみる
適当に書いているので、参考程度に
4つのチェックボックス(リンゴ、ミカン、バナナ、ブドウ)がありリンゴとバナナを選択したという設定
$post_data = ['1', '4']; //appleとbananaのデータを受け取った場合
$bit_sum = 0; //受け取りデータ格納用変数
//整数を2進数で表記するために「0b」を先頭につけている(※コメントのため不要ではあるが区別するために)
//1.各選択肢に、それぞれ2進数の各桁が1となるよう値を振っていく。
$fruits = [
1 => 'apple', //0b0001 → 2^0 = 1
2 => 'orange', //0b0010 → 2^1 = 2
4 => 'banana', //0b0100 → 2^2 = 4
8 => 'grape' //0b1000 → 2^3 = 8
];
//2.データをまとめるときは、ビット和(論理和)で計算する
foreach ($post_data as $value) {
$bit_sum = $bit_sum | $value;
}
$selects = [];
//3.まとめたデータから元の選択肢に戻す場合は、ビット積(論理積)を使う
foreach (array_keys($fruits) as $key) {
if ($key & $bit_sum) {
$selects[] = $key;
}
}
print_r($selects); //[1, 4] つまりappleとbananaが格納されているので無事選択肢データを取得できている
解説
1.それぞれ2進数の各桁が1となるように値を振っていく
//1.各選択肢に、それぞれ2進数の各桁が1となるよう値を振っていく。
$fruits = [
1 => 'apple', //0b0001 → 2^0 = 1
2 => 'orange', //0b0010 → 2^1 = 2
4 => 'banana', //0b0100 → 2^2 = 4
8 => 'grape' //0b1000 → 2^3 = 8
];
各桁とデータが対応している。
「0b0001」は、(2^0 × 1) + (2^1 × 0) + (2^2 × 0) + (2^3 × 0) = 1
「0b0010」は、(2^0 × 0) + (2^1 × 1) + (2^2 × 0) + (2^3 × 0) = 2
「0b0100」は、(2^0 × 0) + (2^1 × 0) + (2^2 × 1) + (2^3 × 0) = 4
「0b1000」は、(2^0 × 0) + (2^1 × 0) + (2^2 × 0) + (2^3 × 1) = 8
となっている。
2.データを1つにまとめる
データをまとめるときは、ビット和(論理和)
を行う。
ビット和とは、下記のような条件の元計算する。計算記号は|
。
・どちらか1つでも1
なら1
・どちらも0
の場合0
//ビット和計算例
$value1 = 0b0010 //2 0010
$value2 = 0b1000 //8 | 1000
// -------
echo decbin($value1 | $value2); //0b1010 1010
$post_data = ['1', '4']; //appleとbananaのデータを受け取った場合
//2.データをまとめるときは、ビット和(論理和)を行う
foreach ($post_data as $value) {
$bit_sum = $bit_sum | $value;
}
/*今回の処理の場合
1ループ目 0b0000(0) | 0b0001(1) = 0b0001(1)
2ループ目 0b0001(1) | 0b0100(4) = 0b0101(5)
つまりappleとbananのデータをビット和による計算で「0b0101(5)」という一つのデータにした。
これで1つのカラムに複数データを保存できる!!!
*/
3.まとめたデータから元の選択肢を求める
まとめたデータから元の選択肢に戻す場合は、ビット積(論理積)
を使う。
ビット積とは、下記のような条件の元計算する。計算記号は&
。
・どちらも1
の場合1
・どちらか1つでも0
の場合0
//ビット積計算例
$value1 = 0b0010 //2 0010
$value2 = 0b1010 //10 & 1010
// -------
echo decbin($value1 & $value2); //0b0010 0010
$selects = [];
//3.まとめたデータから元の選択肢に戻す場合は、ビット積(論理積)を使う
foreach (array_keys($fruits) as $key) {
if ($key & $bit_sum) {
$selects[] = $key;
}
}
/*
array_keysを使っているため若干分かりにくくなっているが要はこのループは
「各選択肢に振られている番号」と「ビット和(1つにまとめたデータ)」をビット積で計算し、該当したもの(今回であればリンゴ:1とバナナ:4)を配列に格納している。
2.で計算した通り $bit_sum = 0b0101(5)
1ループ目(リンゴ) 0b0001(1) & 0b0101(5) → 0b0001(1)
2ループ目(ミカン) 0b0010(2) & 0b0101(5) → 0b0000(0)
3ループ目(バナナ) 0b0100(4) & 0b0101(5) → 0b0100(4)
4ループ目(ブドウ) 0b1000(8) & 0b0101(5) → 0b0000(0)
このような計算結果となる。
0は論理値でfalseとなるため配列への格納処理は行われない。
1,4は論理値でtrueとなるため配列への格納処理が行われる。
よって、今回受け取ったリンゴとバナナのデータをビット和から再び求まる。
selects = ['1', '4']
*/
参考
まとめ
今回は、ビット演算についてまとめた。
ビット演算を実際に使うのは初めてで感動した。
以上。