我们有一些大的二进制数N(大意味着数百万的数字)。我们还有二进制掩码M,其中1表示我们必须删除数字N中此位置的数字,并将所有较高位移动一个位置。
示例:
N = 100011101110
M = 000010001000
Res 1000110110
是否有可能在没有使用某些逻辑或算术运算的循环的情况下解决此问题?我们可以假设我们可以在Python中访问bignum算法。
感觉它应该是这样的: Res = N - (N xor M) 但它不起作用
UPD :我目前的循环解决方案如下:
def prepare_reduced_arrays(dict_of_N, mask):
'''
mask: string '0000011000'
each element of dict_of_N - big python integer
'''
capacity = len(mask)
answer = dict()
for el in dict_of_N:
answer[el] = 0
new_capacity = 0
for i in range(capacity - 1, -1, -1):
if mask[i] == '1':
continue
cap2 = (1 << new_capacity)
pos = (capacity - i - 1)
for el in dict_of_N:
current_bit = (dict_of_N[el] >> pos) & 1
if current_bit:
answer[el] |= cap2
new_capacity += 1
return answer, new_capacity
答案 0 :(得分:4)
虽然如果没有python中的循环,这可能是不可能的,但是numba
并且只是及时编译它可以非常快。我假设您的输入可以很容易地表示为布尔数组,使用struct
从二进制文件构造起来非常简单。我实现的方法涉及迭代几个不同的对象,但是仔细选择这些迭代以确保它们是编译器优化的,并且从不做两次相同的工作。第一次迭代使用np.where
来定位要删除的所有位的索引。这个特定的功能(以及许多其他功能)由numba编译器优化。然后,我使用这个位索引列表来构建要保留的位片的片段索引。最后一个循环将这些切片复制到一个空的输出数组。
import numpy as np
from numba import jit
from time import time
def binary_mask(num, mask):
num_nbits = num.shape[0] #how many bits are in our big num
mask_bits = np.where(mask)[0] #which bits are we deleting
mask_n_bits = mask_bits.shape[0] #how many bits are we deleting
start = np.empty(mask_n_bits + 1, dtype=int) #preallocate array for slice start indexes
start[0] = 0 #first slice starts at 0
start[1:] = mask_bits + 1 #subsequent slices start 1 after each True bit in mask
end = np.empty(mask_n_bits + 1, dtype=int) #preallocate array for slice end indexes
end[:mask_n_bits] = mask_bits #each slice ends on (but does not include) True bits in the mask
end[mask_n_bits] = num_nbits + 1 #last slice goes all the way to the end
out = np.empty(num_nbits - mask_n_bits, dtype=np.uint8) #preallocate return array
for i in range(mask_n_bits + 1): #for each slice
a = start[i] #use local variables to reduce number of lookups
b = end[i]
c = a - i
d = b - i
out[c:d] = num[a:b] #copy slices
return out
jit_binary_mask = jit("b1[:](b1[:], b1[:])")(binary_mask) #decorator without syntax sugar
###################### Benchmark ########################
bignum = np.random.randint(0,2,1000000, dtype=bool) # 1 million random bits
bigmask = np.random.randint(0,10,1000000, dtype=np.uint8)==9 #delete about 1 in 10 bits
t = time()
for _ in range(10): #10 cycles of just numpy implementation
out = binary_mask(bignum, bigmask)
print(f"non-jit: {time()-t} seconds")
t = time()
out = jit_binary_mask(bignum, bigmask) #once ahead of time to compile
compile_and_run = time() - t
t = time()
for _ in range(10): #10 cycles of compiled numpy implementation
out = jit_binary_mask(bignum, bigmask)
jit_runtime = time()-t
print(f"jit: {jit_runtime} seconds")
print(f"estimated compile_time: {compile_and_run - jit_runtime/10}")
在这个例子中,我对一个长度为1,000,000的布尔数组执行基准测试,对于编译和未编译的版本,总共执行10次。在我的笔记本电脑上,输出是:
non-jit: 1.865583896636963 seconds jit: 0.06370806694030762 seconds estimated compile_time: 0.1652850866317749
正如您可以通过这样的简单算法看到的,从编译中可以看到非常显着的性能提升。 (在我的情况下,大约20-30倍加速)
答案 1 :(得分:3)
据我所知,只有在M
是2的幂时,才能在不使用循环的情况下完成此操作。
我们以你的例子为例,修改M
以使它具有2的幂:
N = 0b100011101110 = 2286
M = 0b000000001000 = 8
从N
中删除第四个最低位并将高位向右移动将导致:
N = 0b10001110110 = 1142
我们使用以下算法实现了这一目标:
N = 0b100011101110 = 2286
M
中最重要的位到最低位重复。M
中的当前位设置为1,则将较低位存储在某个变量x
中:
x = 0b1101110
M
中减去N
中的当前位,包括当前位,以便我们最终得到以下结果:
N - (0b10000000 + x) = N - (0b10000000 + 0b1101110) = 0b100011101110 - 0b11101110 = 0b100000000000
0
来实现,这可能更有效。0b100000000000 >> 1 = 0b10000000000
x
添加回移位结果:
0b10000000000 + x = 0b10000000000 + 0b1101110 = 0b10001101110 = 1142
如果你只是简单地迭代M
(从最重要的位到最低有效位)并在每个设置位上执行此过程,因为时间复杂度为O(M.bit_length())
。
我也写了这个算法的代码,我相信它相对有效,但我没有任何大的二进制数来测试它:
def remove_bits(N, M):
bit = 2 ** (M.bit_length() - 1)
while bit != 0:
if M & bit:
ones = bit - 1
# Store lower `bit` bits.
temp = N & ones
# Clear lower `bit` bits.
N &= ~ones
# Shift once to the right.
N >>= 1
# Set stored lower `bit` bits.
N |= temp
bit >>= 1
return N
if __name__ == '__main__':
N = 0b100011101110
M = 0b000010001000
print(bin(remove_bits(N, M)))
使用您的示例,返回结果:0b1000110110
答案 2 :(得分:2)
我认为在内置的按位运算符的常数调用中没有任何方法可以做到这一点。为了实现这一点,Python必须提供类似PEXT
的东西。
对于数以百万计的数字,您实际上可以通过在位序列方面工作,牺牲Python整体的空间优势以及按位操作的时间优势来获得最佳性能,从而有利于您可以执行的操作的更大灵活性。我不知道盈亏平衡点在哪里:
import itertools
bits = bin(N)[2:]
maskbits = bin(M)[2:].zfill(len(bits))
bits = bits.zfill(len(maskbits))
chosenbits = itertools.compress(bits, map('0'.__eq__, maskbits))
result = int(''.join(chosenbits), 2)