web-dev-qa-db-ja.com

正の整数の非ゼロビットをカウントする高速な方法

Pythonで整数のビット数をカウントする高速な方法が必要です。私の現在のソリューションは

bin(n).count("1")

しかし、これを行うより速い方法があるかどうか疑問に思っていますか?

PS:(私は大きな2Dバイナリ配列を数値の単一リストとして表現し、ビット単位の操作を行っています。これにより、時間は数時間から数分に短縮されます。

編集:1. python 2.7または2.6にある必要があります

明確なボトルネックにはならないので、小さな数値の最適化はそれほど重要ではありませんが、一部の場所には10000 +ビットの数値があります

たとえば、これは2000ビットの場合です。

12448057941136394342297748548545082997815840357634948550739612798732309975923280685245876950055614362283769710705811182976142803324242407017104841062064840113262840137625582646683068904149296501029754654149991842951570880471230098259905004533869130509989042199261339990315125973721454059973605358766253998615919997174542922163484086066438120268185904663422979603026066685824578356173882166747093246377302371176167843247359636030248569148734824287739046916641832890744168385253915508446422276378715722482359321205673933317512861336054835392844676749610712462818600179225635467147870208L
101
zidarsk8

任意の長さの整数の場合、bin(n).count("1")は純粋なPythonで見つけることができる最速です。

オスカーとアダムのソリューションを、それぞれ64ビットと32ビットのチャンクで整数を処理するように適合させてみました。両方ともbin(n).count("1")よりも少なくとも10倍遅かった(32ビットバージョンは再び約半分の時間を要した)。

一方、 gmpypopcount()bin(n).count("1")の約1/20時間かかりました。したがって、gmpyをインストールできる場合は、それを使用してください。

コメントの質問に答えるために、バイトについてはルックアップテーブルを使用します。実行時に生成できます:

counts = bytes(bin(x).count("1") for x in range(256))  # py2: use bytearray

または、そのまま文字通りに定義します。

counts = (b'\x00\x01\x01\x02\x01\x02\x02\x03\x01\x02\x02\x03\x02\x03\x03\x04'
          b'\x01\x02\x02\x03\x02\x03\x03\x04\x02\x03\x03\x04\x03\x04\x04\x05'
          b'\x01\x02\x02\x03\x02\x03\x03\x04\x02\x03\x03\x04\x03\x04\x04\x05'
          b'\x02\x03\x03\x04\x03\x04\x04\x05\x03\x04\x04\x05\x04\x05\x05\x06'
          b'\x01\x02\x02\x03\x02\x03\x03\x04\x02\x03\x03\x04\x03\x04\x04\x05'
          b'\x02\x03\x03\x04\x03\x04\x04\x05\x03\x04\x04\x05\x04\x05\x05\x06'
          b'\x02\x03\x03\x04\x03\x04\x04\x05\x03\x04\x04\x05\x04\x05\x05\x06'
          b'\x03\x04\x04\x05\x04\x05\x05\x06\x04\x05\x05\x06\x05\x06\x06\x07'
          b'\x01\x02\x02\x03\x02\x03\x03\x04\x02\x03\x03\x04\x03\x04\x04\x05'
          b'\x02\x03\x03\x04\x03\x04\x04\x05\x03\x04\x04\x05\x04\x05\x05\x06'
          b'\x02\x03\x03\x04\x03\x04\x04\x05\x03\x04\x04\x05\x04\x05\x05\x06'
          b'\x03\x04\x04\x05\x04\x05\x05\x06\x04\x05\x05\x06\x05\x06\x06\x07'
          b'\x02\x03\x03\x04\x03\x04\x04\x05\x03\x04\x04\x05\x04\x05\x05\x06'
          b'\x03\x04\x04\x05\x04\x05\x05\x06\x04\x05\x05\x06\x05\x06\x06\x07'
          b'\x03\x04\x04\x05\x04\x05\x05\x06\x04\x05\x05\x06\x05\x06\x06\x07'
          b'\x04\x05\x05\x06\x05\x06\x06\x07\x05\x06\x06\x07\x06\x07\x07\x08')

次に、counts[x]xの1ビットの数を取得します(0≤x≤255)。

108
kindall

次のアルゴリズムを適応できます。

def CountBits(n):
  n = (n & 0x5555555555555555) + ((n & 0xAAAAAAAAAAAAAAAA) >> 1)
  n = (n & 0x3333333333333333) + ((n & 0xCCCCCCCCCCCCCCCC) >> 2)
  n = (n & 0x0F0F0F0F0F0F0F0F) + ((n & 0xF0F0F0F0F0F0F0F0) >> 4)
  n = (n & 0x00FF00FF00FF00FF) + ((n & 0xFF00FF00FF00FF00) >> 8)
  n = (n & 0x0000FFFF0000FFFF) + ((n & 0xFFFF0000FFFF0000) >> 16)
  n = (n & 0x00000000FFFFFFFF) + ((n & 0xFFFFFFFF00000000) >> 32) # This last & isn't strictly necessary.
  return n

これは64ビットの正の数に対して機能しますが、簡単に拡張可能であり、引数の対数を使用して(つまり、引数のビットサイズを直線的に使用して)操作の数を増やすことができます。

これがどのように機能するかを理解するために、64ビット文字列全体を64個の1ビットバケットに分割することを想像してください。各バケットの値は、バケットに設定されているビット数に等しくなります(ビットが設定されていない場合は0、1ビットが設定されている場合は1)。最初の変換の結果は類似した状態になりますが、各バケットが2ビット長の32バケットです。これは、バケットを適切にシフトし、その値を追加することで実現されます(バケット間でキャリーが発生しないため、1回の追加ですべてのバケットが処理されます。nビット数は常にnをエンコードするのに十分な長さです)。さらに変換すると、64ビットの長いバケットに到達するまで、指数関数的にサイズが大きくなるバケットの数が指数関数的に減少する状態になります。これにより、元の引数に設定されたビット数が得られます。

22
Adam Zalcman

以下に説明するように、人口カウントアルゴリズムのPython実装があります post

def numberOfSetBits(i):
    i = i - ((i >> 1) & 0x55555555)
    i = (i & 0x33333333) + ((i >> 2) & 0x33333333)
    return (((i + (i >> 4) & 0xF0F0F0F) * 0x1010101) & 0xffffffff) >> 24

0 <= i < 0x100000000で機能します。

13
Óscar López

この post によると、これは Hamming weight の最速の実装のようです(約64KBのメモリを使用してもかまいません)。

#http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetTable
POPCOUNT_TABLE16 = [0] * 2**16
for index in range(len(POPCOUNT_TABLE16)):
    POPCOUNT_TABLE16[index] = (index & 1) + POPCOUNT_TABLE16[index >> 1]

def popcount32_table16(v):
    return (POPCOUNT_TABLE16[ v        & 0xffff] +
            POPCOUNT_TABLE16[(v >> 16) & 0xffff])

Python 2.xでは、rangexrangeに置き換える必要があります。

編集

より良いパフォーマンスが必要な場合(および数値が大きな整数である場合)、 GMP ライブラリをご覧ください。これには、さまざまなアーキテクチャ向けの手書きのアセンブリ実装が含まれています。

gmpy は、GMPライブラリをラップするCコードPython拡張モジュールです。

>>> import gmpy
>>> gmpy.popcount(2**1024-1)
1024
8
Paolo Moretti

私はこの方法が本当に好きです。シンプルで非常に高速ですが、pythonには無限の整数があるため、ビット長に制限はありません。

ゼロをスキャンする時間を無駄にしないため、実際には見た目よりもunningです。たとえば、1000000000000000000000010100000001の設定ビットを1111でカウントするのと同じ時間がかかります。

def get_bit_count(value):
   n = 0
   while value:
      n += 1
      value &= value-1
   return n
3
Robotbugs

あなたはNumpyが遅すぎると言った。個々のビットを保存するためにそれを使用していましたか? intをビット配列として使用するという考え方を拡張せず、Numpyを使用してそれらを格納するのはなぜですか?

Nビットをceil(n/32.) 32ビット整数の配列として格納します。その後、intを使用するのと同じ(まあ、十分に似た)方法でnumpy配列を操作できます。これには、intを使用して別の配列にインデックスを付けることも含まれます。

アルゴリズムは、基本的に、各セルに設定されているビット数を並行して計算し、各セルのビットカウントを合計することです。

setup = """
import numpy as np
#Using Paolo Moretti's answer http://stackoverflow.com/a/9829855/2963903
POPCOUNT_TABLE16 = np.zeros(2**16, dtype=int) #has to be an array

for index in range(len(POPCOUNT_TABLE16)):
    POPCOUNT_TABLE16[index] = (index & 1) + POPCOUNT_TABLE16[index >> 1]

def popcount32_table16(v):
    return (POPCOUNT_TABLE16[ v        & 0xffff] +
            POPCOUNT_TABLE16[(v >> 16) & 0xffff])

def count1s(v):
    return popcount32_table16(v).sum()

v1 = np.arange(1000)*1234567                       #numpy array
v2 = sum(int(x)<<(32*i) for i, x in enumerate(v1)) #single int
"""
from timeit import timeit

timeit("count1s(v1)", setup=setup)        #49.55184188873349
timeit("bin(v2).count('1')", setup=setup) #225.1857464598633

びっくりしたが、Cモジュールを書くことを誰も提案しなかった。

2
leewz

アルゴリズムを使用して、整数のバイナリ文字列[1]を取得できますが、文字列を連結する代わりに、1の数をカウントします。

def count_ones(a):
    s = 0
    t = {'0':0, '1':1, '2':1, '3':2, '4':1, '5':2, '6':2, '7':3}
    for c in oct(a)[1:]:
        s += t[c]
    return s

[1] https://wiki.python.org/moin/BitManipulation

1
Manuel
#Python prg to count set bits
#Function to count set bits
def bin(n):
    count=0
    while(n>=1):
        if(n%2==0):
            n=n//2
        else:
            count+=1
            n=n//2
    print("Count of set bits:",count)
#Fetch the input from user
num=int(input("Enter number: "))
#Output
bin(num)

0
Praveen Narala