我需要一种快速的方法来计算python中整数的位数。 我目前的解决方案是
bin(n).count("1")
但我想知道是否有更快的方法呢?
PS :(我将一个大的2D二进制数组表示为数字和按位操作的单一列表,这会将时间从几小时缩短到几分钟。现在我想摆脱那些额外的分钟。< / p>
编辑: 1.它必须在python 2.7或2.6
并优化小数并不重要,因为那不是一个明确的瓶颈,但我确实在某些地方有10 000 +位的数字
例如这是一个2000位的情况:
12448057941136394342297748548545082997815840357634948550739612798732309975923280685245876950055614362283769710705811182976142803324242407017104841062064840113262840137625582646683068904149296501029754654149991842951570880471230098259905004533869130509989042199261339990315125973721454059973605358766253998615919997174542922163484086066438120268185904663422979603026066685824578356173882166747093246377302371176167843247359636030248569148734824287739046916641832890744168385253915508446422276378715722482359321205673933317512861336054835392844676749610712462818600179225635467147870208L
答案 0 :(得分:104)
对于任意长度的整数,bin(n).count("1")
是我在纯Python中找到的最快的。
我尝试调整Óscar和Adam的解决方案,分别处理64位和32位块中的整数。两者都比bin(n).count("1")
慢了至少十倍(32位版本再次占用了大约一半的时间)。
另一方面,gmpy popcount()
约占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。
答案 1 :(得分:22)
您可以调整以下算法:
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)。第一次转换导致类似的状态,但是每个2位长有32个桶。这是通过适当地移动桶并添加它们的值来实现的(一个添加处理所有桶,因为桶之间不会发生进位 - n位数总是足够长以编码数n)。在我们到达一个64位长的桶之前,进一步的转换会导致状态呈指数级增长,呈指数级增长。这给出了原始参数中设置的位数。
答案 2 :(得分:13)
这是人口计数算法的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
。
答案 3 :(得分:8)
根据这个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上,您应该将range
替换为xrange
。
如果您需要更好的性能(并且您的数字是大整数),请查看GMP
库。它包含许多不同体系结构的手写程序集实现。
gmpy
是一个C编码的Python扩展模块,它包装了GMP库。
>>> import gmpy
>>> gmpy.popcount(2**1024-1)
1024
答案 4 :(得分:3)
Python 3.10引入了int.bit_count()
:
#dictionary to match keys againts words in text filee
dict = {"a": ["ah0", "ey1"], "a's": ["ey1 z"], "a.": ["ey1"], "a.'s": ["ey1 z"]}
# Read from text filee
open_file = open('sampletext.txt', 'r')
lines = open_file.readlines()
open_file.close()
#search the word extracted from textfile, if found in dictionary then print list into the file
for word in lines:
if word in dict:
write_to_file = open('outputfile.txt', 'w')
write_to_file.writelines(str(dict[word]))
write_to_file.close()
在功能上等效于>>> n = 19
>>> bin(n)
'0b10011'
>>> n.bit_count()
3
>>> (-n).bit_count()
3
,但应为~6 times faster。另请参见Issue29882。
答案 5 :(得分:2)
你说Numpy太慢了。你用它来存储个别位吗?为什么不扩展使用int作为位数组但使用Numpy来存储它们的想法?
将n位存储为ceil(n/32.)
32位整数的数组。然后,您可以使用numpy数组使用相同(足够相似)的方式,包括使用它们来索引另一个数组。
该算法基本上是并行计算每个单元中设置的位数,并且它们总结了每个单元的bitcount。
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模块。
答案 6 :(得分:1)
您可以使用该算法获取整数的二进制字符串[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
答案 7 :(得分:1)
我真的很喜欢这种方法。它简单而快速,但由于python具有无限整数,因此位长不受限制。
实际上,它比看上去要狡猾得多,因为它避免了浪费时间扫描零。例如,与1111中一样,需要花费相同的时间来计算1000000000000000000000010100000001中的设置位。
def get_bit_count(value):
n = 0
while value:
n += 1
value &= value-1
return n
答案 8 :(得分:1)
可以将查找表与int.to_bytes
组合在一起(仅适用于Python 3):
popcount8bit = bytes([popcount(x) for x in range(1<<8)]) # use any method to initialize this lookup table
popcount = lambda x: sum(map(popcount8bit.__getitem__,
x.to_bytes((x.bit_length()+7)//8, "little")))
不幸的是,该解决方案比Python 3上的bin(x).count('1')
慢20%,但在PyPy3上快两倍。
这是一个基准脚本,比较了此处针对不同位数的几种不同解决方案:
from __future__ import print_function #for Python 2
import sys
from timeit import timeit
import random
def popcount(x): return bin(x).count("1")
version3=sys.version.startswith("3")
for numBit in (2, 4, 8, 16, 31, 32, 63, 64, 1000, 10000):
maximum=int((1<<numBit)-1) #int cast just in case it overflows to long in Python 2
functions=[
(popcount, "bin count"),
(lambda x: "{:b}".format(x).count("1"), "format string count"),
]
try:
import gmpy
functions.append((gmpy.popcount, "gmpy"))
except ImportError:
pass
if sys.version.startswith("3"):
exec('''functions.append((lambda x: f"{x:b}".count("1"), "f-string count"))''')
if numBit<=16:
table1=[popcount(x) for x in range(maximum+1)]
functions.append((lambda x: table1[x], "lookup list"))
functions.append((table1.__getitem__, "lookup list without lambda"))
table2="".join(map(chr, table1))
functions.append((lambda x: ord(table2[x]), "lookup str"))
if version3:
table3=bytes(table1)
functions.append((lambda x: table3[x], "lookup bytes"))
if numBit==8:
functions.append((
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'
.__getitem__, "lookup bytes hard coded 8 bit"))
table_hardcoded=(
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')
functions.append((
table_hardcoded.__getitem__, "lookup bytes hard coded 8 bit local variable"))
functions.append((table3.__getitem__, "lookup bytes without lambda"))
if version3:
popcount8bit=bytes([popcount(x) for x in range(1<<8)]) #bytes because benchmark says that it's fastest
functions.append((
lambda x: sum(popcount8bit[x] for x in x.to_bytes((x.bit_length()+7)//8, "big")),
"to_bytes"
))
functions.append((
lambda x: sum(map(popcount8bit.__getitem__, x.to_bytes((x.bit_length()+7)//8, "big"))),
"to_bytes without list comprehension"
))
functions.append((
lambda x: sum(map(popcount8bit.__getitem__, x.to_bytes((x.bit_length()+7)//8, "little"))),
"to_bytes little endian, without list comprehension"
))
#for x in (2, 4, 8, 16, 32, 64):
# table1=[popcount(x) for x in range(1<<8)]
print("====== numBit=", numBit)
data=[]
numRepeat=10**7//(numBit+100)
for popcountFunction, description in functions:
random.seed(10) #make randint returns the same value
data.append((
timeit(lambda: popcountFunction(random.randint(0, maximum)), number=numRepeat),
description
))
time1, name1=data[0]
assert name1=="bin count"
data.sort()
maxLength=0
for time, description in data:
maxLength=max(maxLength, len(description))
for time, description in data:
print("{:{}} -> {:2f} = {} * {:2f}".format(description, maxLength+2, time, name1, time/time1))
它同时适用于Python 2和3;但是,如果无法使用Python 2解决方案,则无法衡量。
此处未列出某些解决方案。
结果:
to_bytes
版本变得更快(比“ bin count”快两倍);但是,我无法安装gmpy。答案 9 :(得分: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)
答案 10 :(得分:-1)
事实证明,您的起始表示是一个包含1或0的整数列表的列表。只需用该表示计算它们。
整数中的位数在python中是常量。
但是,如果要计算设置位数,最快的方法是创建符合以下伪代码的列表:[numberofsetbits(n) for n in range(MAXINT)]
这将为您提供生成列表后的恒定时间查找。请参阅@ PaoloMoretti的答案,以便很好地实现这一点。当然,您不必将这一切保留在内存中 - 您可以使用某种持久性键值存储,甚至是MySql。 (另一种选择是实现您自己的简单的基于磁盘的存储)。