为了解决一个研究问题,我们必须在python中组织位掩码搜索。 作为输入,我们有一个原始数据(我们将其表示为一系列位)。尺寸约为1,5Gb。 作为输出,我们必须得到特定位掩码的出现次数。 让我举一个例子来描述情况
input: sequence of bits, a bitmask to search(mask length: 12bits)
第一个想法(效率不高)就是像这样使用XOR:
1step: from input we take 12 first bits(position 0 to 11) and make XOR with mask
2step: from input we take bits from 1 to 12 position and XOR with mask ...
让我们先完成2个步骤:
input sequence 100100011110101010110110011010100101010110101010
mask to search: 100100011110
step 1: take first 12 bits from input: 100100011110 and XOR it with mask.
step 2: teke bits from 1 to 12position: 001000111101 and XOR it with mask.
...
问题是:如何组织从输入中取位? 我们能够取前12位,但是如何从1到12位获取我们需要继续下一次迭代的位?
在我们使用python BitString包之前,我们花在搜索所有掩码上的时间都很高。 还有一个。掩码的大小可以是12位到256位。 有什么建议吗?任务必须在python中实现
答案 0 :(得分:4)
您的算法是在数据中搜索“字符串”的天真方式,但幸运的是有更好的算法。 一个例子是KMP algorithm,但还有其他一些可能更适合您的用例。
使用更好的算法,您可以从O(n*m)
的复杂性转变为O(n+m)
。
答案 1 :(得分:2)
如果你的掩码是8位的倍数,你的搜索就变成了一个相对简单的字节比较,任何substring search algorithm就足够了(我会不建议转换为字符串并使用内置的在搜索中,因为您可能会遇到字符验证失败问题。)
sequence = <list of 8-bit integers>
mask = [0b10010001, 0b01101101]
matches = my_substring_search(sequence, mask)
对于大于8位但不是8的倍数的掩码,我建议将掩码截断为8的倍数并使用与上面相同的子串搜索。然后,对于找到的任何匹配,您可以测试剩余部分。
sequence = <list of 8-bit integers>
mask_a = [0b10010001]
mask_b = 0b01100000
mask_b_pattern = 0b11110000 # relevant bits of mask_b
matches = my_substring_search(sequence, mask_a)
for match in matches:
if (sequence[match+len(mask_a)] & mask_b_pattern) == mask_b:
valid_match = True # or something more useful...
如果sequence
是4096字节的列表,则可能需要考虑各节之间的重叠。这可以通过使sequence
列为4096+ceil(mask_bits/8.0)
字节来轻松完成,但每次读取下一个块时仍然只能前进4096。
作为生成和使用这些面具的演示:
class Mask(object):
def __init__(self, source, source_mask):
self._masks = list(self._generate_masks(source, source_mask))
def match(self, buffer, i, j):
return any(m.match(buffer, i, j) for m in self._masks)
class MaskBits(object):
def __init__(self, pre, pre_mask, match_bytes, post, post_mask):
self.match_bytes = match_bytes
self.pre, self.pre_mask = pre, pre_mask
self.post, self.post_mask = post, post_mask
def __repr__(self):
return '(%02x %02x) (%s) (%02x %02x)' % (self.pre, self.pre_mask,
', '.join('%02x' % m for m in self.match_bytes),
self.post, self.post_mask)
def match(self, buffer, i, j):
return (buffer[i:j] == self.match_bytes and
buffer[i-1] & self.pre_mask == self.pre and
buffer[j] & self.post_mask == self.post)
def _generate_masks(self, src, src_mask):
pre_mask = 0
pre = 0
post_mask = 0
post = 0
while pre_mask != 0xFF:
src_bytes = []
for i in (24, 16, 8, 0):
if (src_mask >> i) & 0xFF == 0xFF:
src_bytes.append((src >> i) & 0xFF)
else:
post_mask = (src_mask >> i) & 0xFF
post = (src >> i) & 0xFF
break
yield self.MaskBits(pre, pre_mask, src_bytes, post, post_mask)
pre += pre
pre_mask += pre_mask
if src & 0x80000000: pre |= 0x00000001
pre_mask |= 0x00000001
src = (src & 0x7FFFFFFF) * 2
src_mask = (src_mask & 0x7FFFFFFF) * 2
此代码不是完整的搜索算法,它是验证匹配的一部分。 Mask对象由源值和源掩码构成,左对齐和(在此实现中)32位长:
src = 0b11101011011011010101001010100000
src_mask = 0b11111111111111111111111111100000
缓冲区是字节值列表:
buffer_1 = [0x7e, 0xb6, 0xd5, 0x2b, 0x88]
Mask对象生成移位掩码的内部列表:
>> m = Mask(src, src_mask)
>> m._masks
[(00 00) (eb, 6d, 52) (a0 e0),
(01 01) (d6, da, a5) (40 c0),
(03 03) (ad, b5, 4a) (80 80),
(07 07) (5b, 6a, 95) (00 00),
(0e 0f) (b6, d5) (2a fe),
(1d 1f) (6d, aa) (54 fc),
(3a 3f) (db, 54) (a8 f8),
(75 7f) (b6, a9) (50 f0)]
中间元素是完全匹配的子字符串(没有简洁的方法可以将此对象从这个对象中取出,但它是m._masks[i].match_bytes
)。一旦使用有效算法查找此子序列,就可以使用m.match(buffer, i, j)
验证周围的位,其中i
是第一个匹配字节的索引,j
是字节的索引在最后一个匹配字节之后(例如buffer[i:j] == match_bytes
)。
在上面的buffer
中,可以从第5位开始找到位序列,这意味着可以在_masks[4].match_bytes
找到buffer[1:3]
。结果:
>> m.match(buffer, 1, 3)
True
(尽可能以任何可能的方式使用,改编,修改,出售或折磨此代码。我非常喜欢将它放在一起 - 这是一个有趣的问题 - 尽管我不会对任何错误负责,所以请确保你彻底测试!)
答案 2 :(得分:1)
在字节数据中搜索位模式比典型搜索更具挑战性。通常的算法并不总是能很好地工作,因为从字节数据中提取每个位有一个成本,并且只有两个字符的“字母”,所以只有偶然50%的比较将匹配(这使得许多算法更多效率低下。
你提到尝试过bitstring模块(我写的),但它太慢了。这对我来说并不太令人惊讶,所以如果有人对如何做到这一点有任何好主意我就会注意!但bittring的方式表明你可以加快速度:
要做匹配bitstring将字节数据的块转换为普通的'0'和'1'字符串,然后使用Python的find
方法进行快速搜索。花费大量时间将数据转换为字符串,但是当您多次搜索相同的数据时,可以节省很多时间。
masks = ['0000101010100101', '010100011110110101101', '01010101101']
byte_data_chunk = bytearray('blahblahblah')
# convert to a string with one character per bit
# uses lookup table not given here!
s = ''.join(BYTE_TO_BITS[x] for x in byte_data_chunk)
for mask in masks:
p = s.find(mask)
# etc.
重点是,一旦转换为普通字符串,您就可以使用内置的find
来优化,以搜索每个掩码。当你使用bitstring时,它必须为每个会破坏性能的掩码进行转换。