快速计数正整数中的非零位的方法


117

我需要一种快速的方法来计算python中整数的位数。我当前的解决方案是

bin(n).count("1")

但我想知道是否有更快的方法?

PS :(我将一个大型2D二进制数组表示为一个数字列表并进行按位运算,这将时间从几小时缩短为几分钟。现在,我想摆脱那些多余的分钟。

编辑:1.它必须在python 2.7或2.6中

对小数字进行优化并不重要,因为这并不是一个明显的瓶颈,但是我确实在某些地方有1万+位的数字

例如,这是一个2000位的情况:

12448057941136394342297748548545082997815840357634948550739612798732309975923280685245876950055614362283769710705811182976142803324242407017104841062064840113262840137625582646683068904149296501029754654149991842951570880471230098259905004533869130509989042199261339990315125973721454059973605358766253998615919997174542922163484086066438120268185904663422979603026066685824578356173882166747093246377302371176167843247359636030248569148734824287739046916641832890744168385253915508446422276378715722482359321205673933317512861336054835392844676749610712462818600179225635467147870208L


1
如果您的“整数”比标准python长,则使用哪种表示形式int?那没有自己的计算方法吗?
Marcin 2012年


3
为了将问题与stackoverflow.com/a/2654211/1959808中的问题区分开来(如果打算有所不同---至少看起来如此),请考虑将标题改写为“ ...计算非-零位...”或类似内容。否则int.bit_length应该是答案,而不是下面接受的答案。
Ioannis Filippidis 2014年

Answers:


121

对于任意长度的整数,这bin(n).count("1")是我在纯Python中可以找到的最快的速度。

我尝试修改Óscar和Adam的解决方案以分别处理64位和32位块中的整数。两者都比至少慢了十倍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')对那些计数naughts ....

11
但是,在不知道要填充多少字节的情况下,您无法真正计数零位,这对于Python长整数来说是有问题的,因为它可能是任何东西。
kindall 2012年

2
尽管这些是单个整数的快速选择,但请注意,其他答案中提出的算法可能会进行矢量化处理,因此,如果在大型numpy数组的许多元素上运行,速度会更快。
Gerrit

对于numpy数组,我将研究以下内容:gist.github.com/aldro61/f604a3fa79b3dec5436a
kindall

1
我已经习惯了bin(n).count("1")。但是,仅超过python提交的60%。@ leetcode
northtree '17

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)。第一次转换会产生类似状态,但有32个存储桶,每个存储桶2位长。这可以通过适当地移动存储桶并添加其值来实现(一次加法处理所有存储桶,因为在存储桶之间不会发生进位-n位数字始终足够长以对数字n进行编码)。进一步的转换导致状态的桶数呈指数级下降,而大小呈指数增长,直到我们得到一个64位长的桶。这给出了原始参数中设置的位数。


我真的不知道这将如何与10000位数字一起工作,但是我确实喜欢这种解决方案。您能否给我一个提示,以及如何将其应用于更大的数字?
zidarsk8 2012年

我没有看到您要处理的位数。您是否考虑过使用像C这样的低级语言编写数据处理代码?也许作为您的python代码的扩展?与python中的大数字相比,您当然可以通过在C中使用大数组来提高性能。就是说,您可以CountBits()通过仅添加8行代码来重写来处理10k位数字。但是由于庞大的常数,它将变得笨拙。
Adam Zalcman 2012年

2
您可以编写代码以生成常量序列,并为处理建立一个循环。
Karl Knechtel 2012年

该答案具有很大的优势,可以将其向量化处理大型numpy数组。
Gerrit

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内numberOfSetBits处理了我的864×64 numpy.ndarray。使用bitCountStr我必须显式地循环,这需要40.7毫秒,或将近50倍的时间。
格里特

8

根据这篇文章,这似乎是汉明重量最快的实现之一(如果您不介意使用大约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

我已经编辑了我的问题,以明确表示我需要使用大数字(10k位及更多)。为32位整数优化某些内容不会产生太大的差异,因为计数的数量必须非常大,在这种情况下,这将导致执行时间变慢。
zidarsk8

但是GMP恰好适用于非常大的数字,包括远远超出您提到的大小的数字。
James Youngman

1
如果将array.array用作POPCOUNT_TABLE16,则内存使用情况会更好,因为它将以整数数组的形式存储,而不是存储为动态大小的Python int对象列表。
gsnedders

6

我真的很喜欢这种方法。它简单而快速,但由于python具有无限整数,因此位长不受限制。

实际上,它比看上去要狡猾得多,因为它避免了浪费时间扫描零。例如,与1111中一样,将需要花费相同的时间来计算1000000000000000000000010100000001中的设置位。

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

看起来不错,但仅适用于非常“稀疏”的整数。平均来说,它很慢。不过,在某些用例中,它看起来确实很有用。
zidarsk8

我不太确定您的意思是“平均来说很慢”。比起什么还算慢?与您未引用的其他某些python代码相比,您的意思是慢吗?它的速度是平均数逐点计数的两倍。实际上,在我的Macbook上,它每秒可计数1260万位,这比我所能计算的要快得多。如果您有另一种适用于整数长度的通用python算法,并且比该算法快,我想听听一下。
Robotbugs

1
我确实接受这实际上比上面的曼努埃尔回答要慢。
Robotbugs

平均而言,速度相当慢,用10000位数字对10000个数字进行计数需要0.15s,bin(n).count("1")但是对于您的函数却需要3.8s。如果数字设置的位数很少,则可以快速运行,但是,如果您选择任何随机数,则平均而言,上述功能会慢几个数量级。
zidarsk8

好吧,我会接受的。我想我只是个家伙,你有点不精确,但你完全正确。在发表评论之前,我还没有使用上面的Manuel的方法测试过该方法。它看起来很笨拙,但实际上非常快。我现在使用的是这样的版本,但是字典中有16个值,这甚至比他引用的版本要快得多。但是从记录来看,我在一个只有几位被设置为1的应用程序中使用了mine,但是对于完全随机的位,是的,它将达到约50:50,并且随着长度的增加,方差减小。
Robotbugs

3

您可以使用该算法获取整数的二进制字符串[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上,[1:]应该为[2:],因为oct()在字符串之前返回“ 0o”。如果您使用hex()而不是oct()并制作16个条目的字典,则代码的运行速度将
大大提高

2

你说Numpy太慢了。您是否使用它来存储单个位?为什么不扩展使用int作为位数组,而是使用Numpy来存储这些位的想法呢?

将n位存储为ceil(n/32.)32位int 数组。然后,您可以使用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。只需在该表示形式中对其进行计数即可。


整数中的位数在python中是恒定的。

但是,如果要计算设置的位数,最快的方法是创建符合以下伪代码的列表: [numberofsetbits(n) for n in range(MAXINT)]

生成列表后,这将为您提供恒定时间的查找。有关此的良好实现,请参见@PaoloMoretti的答案。当然,您不必将所有内容都保留在内存中-您可以使用某种持久键值存储,甚至MySql。(另一种选择是实现您自己的基于磁盘的简单存储)。


@StevenRumbalski怎么样?
Marcin 2012年

当我阅读您的答案时,它仅包含您的第一句话:“整数中的位数在python中是恒定的。”
Steven Rumbalski 2012年

我已经有了一个可能存储的所有计数的位计数查找表,但是拥有大量数字并使用a [i]和a [j]对其进行运算,除非我有10+,否则您的测就无用了GB的RAM。&^ |的数组 三联的10000个数字将是3 * 10000 ^ 3查找表大小。由于我不知道自己需要什么,所以在需要时只计算几千个就更有意义了
zidarsk8 2012年

@ zidarsk8或者,您可以使用某种数据库或持久键值存储。
Marcin 2012年

@ zidarsk8 10 GB以上的ram并不惊人。如果要执行快速的数值计算,则使用中型铁并非没有道理。
Marcin 2012年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.