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


117

Pythonで整数のビット数をカウントする高速な方法が必要です。私の現在の解決策は

bin(n).count("1")

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

PS:(私は大きな2Dバイナリ配列を数値の単一のリストとして表し、ビットごとの演算を行っています。これにより、時間を数時間から数分に短縮できます。次に、それらの余分な分を取り除きたいと思います。

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

小さな数を最適化することはそれほど問題ではありません。ボトルネックになることは明らかではないためですが、一部の場所には10 000 +ビットの数値があります。

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

12448057941136394342297748548545082997815840357634948550739612798732309975923280685245876950055614362283769710705811182976142803324242407017104841062064840113262840137625582646683068904149296501029754654149991842951570880471230098259905004533869130509989042199261339990315125973721454059973605358766253998615919997174542922163484086066438120268185904663422979603026066685824578356173882166747093246377302371176167843247359636030248569148734824287739046916641832890744168385253915508446422276378715722482359321205673933317512861336054835392844676749610712462818600179225635467147870208L


1
「整数」が標準のpythonよりも長い場合、どのような表現を使用していますintか?これには独自の計算方法はありませんか?
Marcin


3
質問をstackoverflow.com/a/2654211/1959808の質問と区別するために(異なるように意図されている場合---少なくともそのように見える場合)、タイトルを「... counting the non-number-ゼロビット...」または類似。それ以外の場合int.bit_lengthは答えであり、以下で受け入れられるものではありません。
Ioannis Filippidis 2014年

回答:


121

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

オスカーのソリューションとアダムのソリューションを適用して、それぞれ64ビットと32ビットのチャンクで整数を処理してみました。どちらも少なくとも10倍遅くなりましたbin(n).count("1")(32ビットバージョンでは、約半分の時間がかかりました)。

一方、gmpy popcount()はの約1/20の時間を要しましたbin(n).count("1")。したがって、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]、0≤x≤255である1ビットの数を取得しxます。


7
+1!これの逆は正確でbin(n).count("0")はありませんが、「0b」という接頭辞のため正確ではありません。bin(n)[2:].count('0')いたずらを数える人のためにある必要があります....
オオカミ

11
ただし、埋めるバイト数がわからない場合は、実際にゼロビットをカウントすることはできません。これは、Pythonのlong integerの場合、何でもかまわないため問題になります。
キンダル

2
これらは単一の整数の高速オプションですが、他の回答で提示されたアルゴリズムはベクトル化される可能性があるため、大きなnumpy配列の多くの要素を実行すると、はるかに高速になることに注意してください。
gerrit 2015年

numpy配列の場合は、次のようなものを調べます。gist.github.com
aldro61

1
使用しましたbin(n).count("1")。ただし、Python送信の60%に勝っています。@ leetcode
ノース

29

次のアルゴリズムを採用できます。

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ビット長のバケットが1つに達するまで、サイズが指数関数的に増大するバケット数が指数関数的に減少する状態になります。これは、元の引数で設定されたビット数を示します。


これが10 000ビットの数値でどのように機能するかは真剣に知りませんが、私はソリューションが好きです。大きな数字にそれを当てはめることができるかどうか、そしてどのようにヒントを教えてくれますか?
zidarsk8 2012年

ここで扱っているビット数がわかりませんでした。Cのような低水準言語でデータ処理コードを書くことを検討しましたか?多分あなたのPythonコードの拡張として?Pythonの大きな数値と比較して、Cで大きな配列を使用することにより、パフォーマンスを確実に向上させることができます。つまり、CountBits()8行のコードを追加するだけで10kビットの数値を処理するようにを書き換えることができます。ただし、定数が大きいため扱いにくくなります。
アダムZalcman

2
定数のシーケンスを生成するコードを記述し、処理用のループを設定できます。
Karl Knechtel 2012年

この回答には、大きな配列を扱う場合にベクトル化できるという大きな利点がありnumpyます。
gerrit 2015年

17

この投稿で説明されている、人口カウントアルゴリズムのPython実装を次に示します。

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ます。


それは賢いです。ヒップからの回答を撮影するのではなく、これを見上げることは完全に適切です!
MrGomez 2012年

1
これをベンチマークしましたか?Python 2.7を使用している私のマシンでは、これは実際にはよりも少し遅いことがわかりましたbin(n).count("1")
David Weldon

@DavidWeldonいいえ、しませんでした。ベンチマークを投稿していただけませんか?
オスカー・ロペス

%timeit numberOfSetBits(23544235423)1000000 loops, best of 3: 818 ns per loop; %timeit bitCountStr(23544235423)1000000 loops, best of 3: 577 ns per loop
gerrit 2015年

7
ただし、841 µsで864x64をnumberOfSetBits処理しますnumpy.ndarray。とbitCountStr私は明示的にループする必要があり、それは40.7ミリ秒、またはほぼ50倍長くかかります。
gerrit 2015年

8

この投稿によると、これはハミング重みの最も速い実装の1つであるようです(約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では、に置き換える必要がrangeありxrangeます。

編集する

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

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

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

質問を編集して、大きな数(10kビット以上)でこれが必要であることを明確にしました。32ビット整数用に何かを最適化しても、カウント数は非常に大きくなる必要があるため、それほど大きな違いはありません。その場合、実行時間が遅くなります。
zidarsk8 2012年

しかし、GMPはまさに、あなたが言及するサイズ以上の数を含む非常に大きな数を対象としています。
James Youngman

1
array.arrayfor を使用するとPOPCOUNT_TABLE16、動的にサイズ設定されたPython intオブジェクトのリストとしてではなく、整数の配列として格納されるため、メモリ使用量が向上します。
gsnedders 2014

6

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

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

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

見栄えは良いですが、非常に「スパース」な整数にのみ適しています。平均してかなり遅いです。それでも、特定のユースケースでは本当に便利に見えます。
zidarsk8

「平均してかなり遅い」という意味がよくわかりません。何に比べてかなり遅い?あなたが引用していない他のいくつかのPythonコードと比較して遅いという意味ですか?これは、平均数をビットごとにカウントする場合の2倍の速度です。実際、私のMacbookでは1秒あたり1260万ビットを数えます。任意の長さの整数で機能し、これよりも高速な別の一般的なpythonアルゴリズムがある場合、それについて聞いてみたいと思います。
Robotbugs

1
上記のマヌエルの回答よりも実際には遅いことは認めます。
Robotbugs

平均でかなり遅い、10000桁の10000桁のビットをカウントすると0.15秒かかりbin(n).count("1")ますが、関数では3.8秒かかりました。数値に設定されているビットが非常に少ない場合は高速に動作しますが、任意の乱数を取ると、平均して上記の関数は桁違いに遅くなります。
zidarsk8

承知しました。私はちょうどあなたが少し不正確なディックcosだったと思いますが、あなたは完全に正しいです。コメントの前に、上記のManuelのメソッドを使用してメソッドをテストしたことがありませんでした。非常に不格好に見えますが、実際には非常に高速です。私は今そのようなバージョンを使用していますが、辞書に16個の値があり、彼が引用したものよりもはるかに高速です。しかし、記録のために、1に設定された数ビットのみのアプリケーションで私のものを使用していましたが、完全にランダムなビットの場合は、長さとともに少しの分散が減少し、約50:50になります。
Robotbugs、

3

アルゴリズムを使用して、整数のバイナリ文字列[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


これは高速に動作します。エラーがあります。少なくともp3では、oct()が文字列の前に '0o'を返すため、[1:]は[2:]である必要があります。oct()の代わりにhex()を使用して16エントリの辞書を作成すると、コードの実行速度が
大幅に向上します

2

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

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

アルゴリズムは基本的に、各セルに設定されたビット数を並行して計算し、それらが各セルのビット数を合計します。

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モジュールを書くことを誰も提案しなかったからです。


0
#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)

-2

最初の表現は、1または0のintのリストのリストであることがわかります。単にその表現でカウントしてください。


整数のビット数はPythonでは一定です。

ただし、設定されたビットの数を数えたい場合、次の疑似コードに準拠したリストを作成するのが最も速い方法です。 [numberofsetbits(n) for n in range(MAXINT)]

これにより、リストを生成した後、一定時間のルックアップが提供されます。これを適切に実装するには、@ PaoloMorettiの回答を参照してください。もちろん、これをすべてメモリに保持する必要はありません。ある種の永続的なKey-Valueストア、またはMySqlを使用することもできます。(別のオプションは、独自のシンプルなディスクベースのストレージを実装することです)。


@StevenRumbalskiどのように役に立たないのですか?
Marcin

私があなたの答えを読んだとき、それはあなたの最初の文だけを含んでいました:「整数のビット数はPythonでは一定です。」
Steven Rumbalski 2012年

格納可能なすべてのカウントのビットカウントルックアップテーブルがすでにありますが、数値の大きなリストがあり、それらをa [i]とa [j]で操作すると、10以上がない限り、解決策は役に立たなくなります。 GBのRAM。&^の配列| 10000の数値のトリプルの場合、3 * 10000 ^ 3のルックアップテーブルサイズになります。何が必要かわからないので、必要なときに数千を数えるだけの方が理にかなっています
zidarsk8

@ zidarsk8または、ある種のデータベースまたは永続的なKey-Valueストアを使用できます。
Marcin

@ zidarsk8 10 GB以上のRAMは驚くほど大きくありません。高速な数値計算を実行したい場合、中型から大型の鉄を使用することは不合理ではありません。
Marcin
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.