问题:快速计数正整数中的非零位的方法
我需要一种快速的方法来计算python中整数的位数。我当前的解决方案是
bin(n).count("1")
但我想知道是否有更快的方法?
PS :(我将一个大型2D二进制数组表示为一个数字列表并进行按位运算,这将时间从几小时缩短为几分钟。现在,我想摆脱那些多余的分钟。
编辑:1.它必须在python 2.7或2.6中
对小数字进行优化并不重要,因为这并不是一个明显的瓶颈,但是我确实在某些地方有1万+位的数字
例如,这是一个2000位的情况:
12448057941136394342297748548545082997815840357634948550739612798732309975923280685245876950055614362283769710705811182976142803324242407017104841062064840113262840137625582646683068904149296501029754654149991842951570880471230098259905004533869130509989042199261339990315125973721454059973605358766253998615919997174542922163484086066438120268185904663422979603026066685824578356173882166747093246377302371176167843247359636030248569148734824287739046916641832890744168385253915508446422276378715722482359321205673933317512861336054835392844676749610712462818600179225635467147870208L
回答 0
对于任意长度的整数,这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
。
回答 1
您可以调整以下算法:
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位长的桶。这给出了原始参数中设置的位数。
回答 2
这里有一个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
。
回答 3
根据这篇文章,这似乎是汉明重量最快的实现之一(如果您不介意使用大约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
回答 4
我真的很喜欢这种方法。它简单而快速,但由于python具有无限整数,因此位长不受限制。
实际上,它比看上去要狡猾得多,因为它避免了浪费时间扫描零。例如,与1111中一样,将需要花费相同的时间来计算1000000000000000000000010100000001中的设置位。
def get_bit_count(value):
n = 0
while value:
n += 1
value &= value-1
return n
回答 5
您可以使用该算法获取整数的二进制字符串[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
回答 6
你说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模块。
回答 7
#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)
回答 8
事实证明,您的起始表示形式是一个整数列表,该整数列表为1或0。只需在该表示形式中对其进行计数即可。
整数中的位数在python中是恒定的。
但是,如果要计算设置的位数,最快的方法是创建符合以下伪代码的列表: [numberofsetbits(n) for n in range(MAXINT)]
生成列表后,这将为您提供恒定时间的查找。有关此的良好实现,请参见@PaoloMoretti的答案。当然,您不必将所有内容都保留在内存中-您可以使用某种持久键值存储,甚至MySql。(另一种选择是实现您自己的基于磁盘的简单存储)。