ある10進数の数について、その数を2進数にしたときに含まれる「1」の数を数える方法を考えます。
Pythonのbin()という組み込み関数を使うと、10進数を2進数に変換できます。
>>> bin(2) '0b10' >>> bin(10) '0b1010' >>> bin(10000) '0b10011100010000' >>> bin(100000) '0b11000011010100000'
10進数2は2進数10で1の数は1、10進数10は2進数1010で1の数は2、10進数10000は2進数10011100010000で1の数は5、10進数100000は2進数11000011010100000で1の数は6、となります。
以下を参照しています。
組み込み関数bin()を使う
- bin()で2進数に変換する。
- 最初の0bを削除して、それぞれの桁の1を足す。
def count_ones_by_bin(num):
bin_num = bin(num)[2:]
count = 0
for i in bin_num:
count += int(i)
return count
一桁ずつビットマスク
一桁ずつ1と&をとっていきます。
10進数10で考えてみます。10進数10は2進数1010でした。
| 1 | 0 | 1 | 0 |
これと10進数1、つまり2進数0001のANDを取ります。
| 0 | 0 | 0 | 1 |
1桁目は(0&1)で0です。
10進数10、つまり2進数1010を1つ右シフトして、また1とANDをとります。
| 0 | 1 | 0 | 1 |
| 0 | 0 | 0 | 1 |
1桁目は(1&1)で1です。 …
…シフト演算を0になるまで続ければ、1の数が分かります。
def count_ones_by_bitmask(num):
count = 0
while num:
count += num & 1
num >>= 1
return count
1を引いてANDをとる
10進数10で考えてみます。10進数10は2進数1010でした。
| 1 | 0 | 1 | 0 |
この数字から1を引きます。
| 1 | 0 | 0 | 1 |
ANDを取ることで一番小さい1を取っている桁を0にすることができます。
def count_ones_by_minus(num):
count = 0
while num:
num &= num -1
count += 1
return count
ビットカウントテーブルを使う
1桁ずつビットマスクをしていたのを、テーブルを作っておくとことで、まとめてやってしまおうというような考え方です。ここでは8ビット単位で考えています。
事前に、0から255までの1の数を数えて、リストにしておきます。
10進数10000は2進数10011100010000です。
| 0 | 0 | 1 | 0 | 0 | 1 | 1 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 |
16進数FFを考えます。
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
ANDを取ります。
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 |
10進数に直すと16です。事前作成していた配列から下8ビットには1つの1が含まれていることが分かります。
次に元の数を8ビット右シフトします。
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 1 | 1 | 1 |
これと16進数FFとのANDを取ります。…
…シフト演算を0になるまで続ければ、1の数が分かります。
def count_ones_by_table(num):
bit_table_256 = [
0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4,
1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8,
]
count = 0
count += bit_table_256[num & 0xFF]
count += bit_table_256[(num >> 8) & 0xFF]
count += bit_table_256[(num >> 16) & 0xFF]
count += bit_table_256[(num >> 24) & 0xFF]
return count
各桁を1の個数と考えてまとめてシフト
以下の40ページ目から分かりやすく説明されています。
def count_ones_by_shift(num):
num = (num & 0x55555555) + ((num & 0xAAAAAAAA) >> 1)
num = (num & 0x33333333) + ((num & 0xCCCCCCCC) >> 2)
num = (num & 0x0F0F0F0F) + ((num & 0xF0F0F0F0) >> 4)
num = (num & 0x00FF00FF) + ((num & 0xFF00FF00) >> 8)
num = (num & 0x0000FFFF) + ((num & 0xFFFF0000) >> 16)
return num
同じことですが、下のようにも書けます。こちらのほうがコードの意味はわかりやすいです。
def count_ones_by_shift_2(num):
num = (num & 0x55555555) + (num >> 1 & 0x55555555)
num = (num & 0x33333333) + (num >> 2 & 0x33333333)
num = (num & 0x0F0F0F0F) + (num >> 4 & 0x0F0F0F0F)
num = (num & 0x00FF00FF) + (num >> 8 & 0x00FF00FF)
num = (num & 0x0000FFFF) + (num >>16 & 0x0000FFFF)
return num