Python 如何跳过内置int中的尾随零位?
有没有一些快速的方法来获取Python3内置整数中尾随的零的数量 例如,如果我想检查是否有4个尾随零,我可以执行以下操作:Python 如何跳过内置int中的尾随零位?,python,python-3.x,performance,Python,Python 3.x,Performance,有没有一些快速的方法来获取Python3内置整数中尾随的零的数量 例如,如果我想检查是否有4个尾随零,我可以执行以下操作: >>> some_integer & 15 或对于8个尾随零: >>> some_integer & 255 如果结果不是0,则在前4位或8位中有一个 有没有办法在一般情况下进行此操作?我的目标是通过>操作符跳过任意数量的尾随零 n = 12 # 0b1100 while n % 2 == 0: n //= 2
>>> some_integer & 15
或对于8个尾随零:
>>> some_integer & 255
如果结果不是0
,则在前4位或8位中有一个
有没有办法在一般情况下进行此操作?我的目标是通过>
操作符跳过任意数量的尾随零
n = 12 # 0b1100
while n % 2 == 0:
n //= 2
# out: 3 i.e. 0b11
这将数字除以2直到不可分割,从而删除尾随的零
编辑:如果n==0
,则实现上述代码无限循环。可更正:
n = 12 # 0b1100
if n != 0:
while n % 2 == 0: n //= 2
# out: 3 i.e. 0b11
编辑:借鉴@MisterMiyagi的建议
n = 10 # 0b1010
if n != 0:
while n % 2 == 0: n, r = divmod(n, 2)
# n = 5 i.e. 0b101
用于任意长数字
针对32位数字的无循环快速高效解决方案
改编自
您可以按尾随零的数量右移并去除它们(在shift=n>>r
中表示)
测试
输出:
n: 1 bin: 0b1 zeroes: 0 shifted: 0b1
n: 2 bin: 0b10 zeroes: 1 shifted: 0b1
n: 3 bin: 0b11 zeroes: 0 shifted: 0b11
n: 4 bin: 0b100 zeroes: 2 shifted: 0b1
n: 5 bin: 0b101 zeroes: 0 shifted: 0b101
n: 6 bin: 0b110 zeroes: 1 shifted: 0b11
n: 7 bin: 0b111 zeroes: 0 shifted: 0b111
n: 8 bin: 0b1000 zeroes: 3 shifted: 0b1
n: 9 bin: 0b1001 zeroes: 0 shifted: 0b1001
n: 10 bin: 0b1010 zeroes: 1 shifted: 0b101
n: 11 bin: 0b1011 zeroes: 0 shifted: 0b1011
n: 12 bin: 0b1100 zeroes: 2 shifted: 0b11
n: 13 bin: 0b1101 zeroes: 0 shifted: 0b1101
n: 14 bin: 0b1110 zeroes: 1 shifted: 0b111
n: 15 bin: 0b1111 zeroes: 0 shifted: 0b1111
n: 16 bin: 0b10000 zeroes: 4 shifted: 0b1
n: 17 bin: 0b10001 zeroes: 0 shifted: 0b10001
n: 18 bin: 0b10010 zeroes: 1 shifted: 0b1001
n: 19 bin: 0b10011 zeroes: 0 shifted: 0b10011
您可以使用位算术(用于正值和负值,但不包括
0
,因为没有1
可跟踪,因此未对其行为进行定义):
或:
或者,可能有更有趣的0
行为(感谢@TomKarzes评论):
为了理解它们是如何工作的,让我们考虑一下最后一个表达式。当我们执行x-1
时,如果x
为奇数,则只有该位被翻转,然后当我们执行xor^
时,该位是操作中唯一幸存的位。类似地,当x
不是奇数时,每个尾随的零被翻转到1
,被跟踪的1
被翻转到0
,所有其他位都不被触及,然后我们进行xor
操作,直到被跟踪的1
的所有位都被设置。然后,计算找到的有效位的数量,-1
只是一个常量偏移量,我们需要得到“尾随零的数量”
为什么第一种方法也有效,是因为负数的两个补码表示。具体细节留给读者作为练习
为了证明它们有效:
for i in range(-15, 16):
print(
f'{i:5b}',
f'{i ^ -i:3d}',
f'{(i & -i).bit_length() - 1:2d}',
f'{(i ^ -i).bit_length() - 2:2d}',
f'{(i ^ (i - 1)).bit_length() - 1:2d}')
-1111-200
-1110 -4 1 1 1
-1101 -2 0 0 0
-1100 -8 2 2 2
-1011 -2 0 0 0
-1010 -4 1 1 1
-1001 -2 0 0 0
-1000 -16 3 3 3
-111 -2 0 0 0
-110 -4 1 1 1
-101 -2 0 0 0
-100 -8 2 2 2
-11 -2 0 0 0
-10 -4 1 1 1
-1 -2 0 0 0
0 0 -1 -2 0
1 -2 0 0 0
10 -4 1 1 1
11 -2 0 0 0
100 -8 2 2 2
101 -2 0 0 0
110 -4 1 1 1
111 -2 0 0 0
1000 -16 3 3 3
1001 -2 0 0 0
1010 -4 1 1 1
1011 -2 0 0 0
1100 -8 2 2 2
1101 -2 0 0 0
1110 -4 1 1 1
1111 -2 0 0 0
请注意0
输入的两个表达式的不同行为。
特别是,(x^(x-1)).bit_length()-1
可能会导致更简单的表达式,如果希望0
输入生成0
输出
速度方面,这应该比任何基于循环的解决方案都要快,并且与基于查找的解决方案相当(但不受大小限制,也不使用额外内存):
最终附加的位移位操作只需要几纳秒,因此在这里应该是不相关的。对于您的用例来说,循环是可以接受的吗?
(number&2**number\u of_training\u zeres-1)
将给您一个布尔值,告诉您的数字中是否有一定数量的尾随零。@AravindSuresh无法右移。此外,它生成的是一个int
,而不是bool
。您是被限制为正整数还是也可以为负整数?@norok2我被限制为正整数请注意,内置的divmod
等于(x//y,x%y)
。利用这一点可能会大大提高性能,因为%
和/
都使用了divmod,但放弃了部分结果。我考虑的是中的一些东西,而True:div,mod=divmod(n,2)
,嵌套分支如果不是mod:n=div
和其他:break
。即使使用赋值表达式,也无法保持这种简洁。由于这种方法无论如何都要比比特魔法慢,所以使用可读性更高的%
和/
操作实际上可能是最好的。@MisterMiyagidivmod
可能会慢。@juanpa.arrivillaga确实如此。:/在Python中解压结果的额外支架是一个杀手。@mistermiagi它太糟糕了,速度太慢了。我一直认为divmod的全部目的是防止两次分割,从而节省一些CPU周期。@interjay这是我的用例的问题,因为我使用任意长精度,thanks@mikulatomas根据新要求添加了解决方案(x^(x-1))。位长度()-1
避免了0
的奇怪结果@TomKarzes我实际上发现0的大小写是特别的,因为它是严格未定义的(应该是0?无穷大?没有1可以跟踪…),但是你也可以使用你的表达式,我认为它实际上应该更快。@norok2对,但是使用0
product0
的优点是,您可以通过它进行右移,而不会产生错误。如果移位量为负数,则会出现错误。@TomKarzes我在代码中避免0
,但谢谢。
for n in range(1, 20):
r = get_number_of_trailing_zeroes(n)
shifted = n >> r
print(f'n: {n} bin: {bin(n)} zeroes: {r} shifted: {bin(shifted)}')
assert 0 == get_number_of_trailing_zeroes(shifted)
n: 1 bin: 0b1 zeroes: 0 shifted: 0b1
n: 2 bin: 0b10 zeroes: 1 shifted: 0b1
n: 3 bin: 0b11 zeroes: 0 shifted: 0b11
n: 4 bin: 0b100 zeroes: 2 shifted: 0b1
n: 5 bin: 0b101 zeroes: 0 shifted: 0b101
n: 6 bin: 0b110 zeroes: 1 shifted: 0b11
n: 7 bin: 0b111 zeroes: 0 shifted: 0b111
n: 8 bin: 0b1000 zeroes: 3 shifted: 0b1
n: 9 bin: 0b1001 zeroes: 0 shifted: 0b1001
n: 10 bin: 0b1010 zeroes: 1 shifted: 0b101
n: 11 bin: 0b1011 zeroes: 0 shifted: 0b1011
n: 12 bin: 0b1100 zeroes: 2 shifted: 0b11
n: 13 bin: 0b1101 zeroes: 0 shifted: 0b1101
n: 14 bin: 0b1110 zeroes: 1 shifted: 0b111
n: 15 bin: 0b1111 zeroes: 0 shifted: 0b1111
n: 16 bin: 0b10000 zeroes: 4 shifted: 0b1
n: 17 bin: 0b10001 zeroes: 0 shifted: 0b10001
n: 18 bin: 0b10010 zeroes: 1 shifted: 0b1001
n: 19 bin: 0b10011 zeroes: 0 shifted: 0b10011
(x & -x).bit_length() - 1
(x ^ -x).bit_lenght() - 2
(x ^ (x - 1)).bit_length() - 1
for i in range(-15, 16):
print(
f'{i:5b}',
f'{i ^ -i:3d}',
f'{(i & -i).bit_length() - 1:2d}',
f'{(i ^ -i).bit_length() - 2:2d}',
f'{(i ^ (i - 1)).bit_length() - 1:2d}')
def trail_zero_loop(x):
if x != 0:
k = -1
r = 0
while not r:
# x, r = divmod(x, 2) # ~15% slower
r = x % 2
x = x // 2
k += 1
return k
else:
return -1
def trail_zero_and(x):
return (x & -x).bit_length() - 1
def trail_zero_xor(x):
if x != 0:
# return (x ^ -x).bit_length() - 2 # ~1% slower
return (x ^ (x - 1)).bit_length() - 1
else:
return -1
_LOOKUP =[ 32, 0, 1, 26, 2, 23, 27, 0, 3, 16, 24, 30, 28, 11, 0, 13, 4, 7, 17, 0, 25, 22, 31, 15, 29, 10, 12, 6, 0, 21, 14, 9, 5, 20, 8, 19, 18 ]
def trail_zero_lookup(x):
if x != 0:
return _LOOKUP[(x & -x) % 37]
else:
return -1
n = 2 ** 30
%timeit trail_zero_loop(n)
# 3.15 µs ± 25.4 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
%timeit trail_zero_and(n)
# 228 ns ± 9.87 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
%timeit trail_zero_xor(n)
# 253 ns ± 7.25 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
%timeit trail_zero_lookup(n)
# 294 ns ± 1.81 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)