如何解决获取所有匹配位掩码的有效方法?
假设我有一些 64 位整数的模式。给定一个 64 位整数 n
如何有效地列出所有匹配 n
的模式?
更准确地说,我对这组感兴趣: {pi 在 (n & pi) == pi}
等模式中感谢您的帮助
解决方法
一种优化的解决方案,复杂度不是 O(log(P))
,但优于 O(P)
LSB => Least Significant Bit
将每个模式映射到一个集合
m[i] = { p for p in patterns if p's ith LSB is zero }
O(P)
复杂度
位置 = Find all non-set bit positions of
N i.e with zero value
O(1) 复杂度
你的答案是
intersection of { patterns,m[i] for i in positions}
k
排序集合的交集可以在 O(N)
中计算,其中 N 是最大集合基数。
当所有位都设置时,这将不起作用,更具体地说,当您的输入是 2^64-1 时。在这种情况下,所有位都已设置,因此您找不到任何未设置位的位置,在这种情况下,打印所有模式。
示例:
p = {1,2,3,4,5,6,7} {b001,b010,b011,b100,b101,b110,b111}
pattern_map =
{
1: [010,100,110],2: [001,101],3: [001,010,011],4: [0001,0010,0011,0100,0101,0110,0111]
}
-
n = 1
b001
第 2 个和第 3 个 LSB 为零
=>
[001,011,101,110,111],[001,101]
和[001,011]
的交集=> [001]
-
n = 2
b010
第一个和第三个 LSB 为零
=>[001,[010,110] 和 [001,011] 的交点
=> [010]
-
n = 3
b011
第三个 LSB 为零
=> [001,111] 和 [001,011] 的交集
=> [001,011]
-
n = 4
b100
第一个和第二个 LSB 为零
=> [001,101] 的交集
=> [100]
-
n = 5
b101
第二个 LSB 为零
=> [001,101] 的交集
=> [001,101]
-
n = 6
b110
第一个 LSB 为零
=>[001,111] 和 [010,110] 的交点
=>[010,110]
-
n = 7 =>
b111
第 4 个 LSB 为零
=> [0001,0111]
如果我正确理解了问题(希望了解更多相关细节)。您可以将 Numpy CommentPage
与 broadcasting
一起用于您的任务。
编辑:我的解决方案是在 python 中,我刚刚意识到,你没有提到语言标签。
np.bitwise_and
import numpy as np
p = np.random.randint(0,5000,(1000,),dtype='int64') #5000 patterns
n = np.random.randint(0,10000,(500,dtype='int64') #10000 integers
compare = np.bitwise_and(n[:,None],p[None,:]) == p[None,:]
#True if (n & pi) == pi,else False
n_idx = 0 #For 0th n integer in my list
p_idx = np.argwhere(compare[0]).flatten() #This is the index of patterns that match
print('integer ->',n[0])
print('matching patterns ->',p[p_idx])
,
幸运的是,您的支票 (n & p1) == p1
是可传递的。因此,您也可以在模式空间上计算该关系并使用它来跳过检查。我称这种关系为 coverage 并使用符号 ▷
作为缩写。如果 p2 中的所有设置位也设置在 p1 中,则 p1 覆盖 p2。正式:
p1 covers p2
iff p1 ▷ p2
iff (p1 & p2) == p2
。
传递性意味着从 n ▷ p1
和 p1 ▷ p2
跟在 n ▷ p2
之后。因此,如果我们已经知道 n ▷ p2
,我们可以省略检查 n ▷ p1
。为了尽可能频繁地使用它,我们必须确保始终在 p1
之前检查 p2
,因为 p1 ▷ p2
。计算这个顺序是第一步:
P = { ... } // set of patterns
operator ▷(p1,p2):
return (p1 & p2) == p2
function initCoverGraph():
// dummy that covers everything
root = 0xFFFFFFFFFFFFFFFF
// max. set of root's covers such that no child covers another
root.children = {}
for p in P:
insertBelow(r,p)
function insertBelow(p1,p2):
isChild = true
for c in p1.children:
if p2 ▷ c1:
p1.children.remove(c)
p2.children.add(c)
if c ▷ p2:
insertBelow(c,p2)
isChild = false
break
if isChild:
p1.children.add(p2)
您可以进一步优化它。从 p1 ▷ p2
跟随 p1 > p2
。要利用这一点,请对 P
和每个 .children
使用排序集,然后将 insertBelow
的循环一分为二,仅迭代 p1.children
的相关部分。
现在我们已经构建了▷-关系的有向无环图(DAG)。对于快速查询,每个节点 p1
还需要 p1.successors
,即 p1
的传递自反子节点。也就是说,p1.successors = {p1} u p1.children u { c.children | c in p1.children } u ...
。您可以使用来自根节点的单个 DFS 来计算它。为简洁起见,我们假设我们已经这样做了。
现在,让我们做一些查询。
function query(n,root,coveredByN={}):
if coveredByN.contains(root):
return
for c in root.children:
if n ▷ c:
coveredByN.addAll(c.successors)
else:
query(n,c,coveredByN)
请注意,query
正确地忽略了虚拟 root
。如果 P
本身包含 p1 = 0xFF...FF(等于 root
,但不相同),则 root.children
包含 p1,结果正确。
这应该已经相当快了。如果需要,您可以使用排序集(如上所述)和适当的数据结构进一步优化。
实际性能在很大程度上取决于您的模式的分布。在某些情况下,从另一边(从 n !▷ p2
和 p1 ▷ p2
跟随 n !▷ p1
)处理问题可能会更快。
最简单的方法是根据模式 0
检查所有数字,从 2^64-1
到 n
。您需要 2^64
步(在 C++ 中,查看矢量化以减少步数)。
没有任何方法可以在 64
步(见下文)中为您提供相同的结果,但您可以改进朴素的方法。
第一个想法:假设您的模式是:
bit 0 0 ... 0 1 ? ? ? ? ? 1 0 ... 0
pos 0 1 ... i ... j ... 63
其中 i
是第一个 1
,j
是最后一个 1
。您可以在 64 个步骤中找到 i
和 j
(检查 2^k
where 0 <= k < 64
)。
显然,没有大于或等于 2^(i+1)
的数字会匹配该模式,因为您在位置 1
之前有一个 i
。因此,您只需检查 2^(i+1)
数字。
现在,让我们考虑 j
:如果数字 p
匹配 n
,那么所有匹配该模式的数字都具有 p + t.2^j
形式,因为:
-
p + k
其中k < t.2^j
在位置1
后面有一个j
,并且 -
p + k
wherek > t.2^j
具有形式(欧几里德除法):(p + q.2^j) + r
wherer < 2^j
。同样,如果r != 0
,则位置1
后面有一个j
。 您认出了步骤。您不必检查每个数字,只需检查2^j
个数字中的一个。
结论:您可以在 64 + 2^(i-j+1)
步中根据模式检查数字。
第二个想法:您可以尝试生成所有数字,而不仅仅是检查。从理论上讲,它可能会更快,但在实践中,我认为这不会有效率。假设模式是:
bit 0 ... 0 1 0 ... 1 ... 1 ... 1 0 ... 0
pos p_1 p_2 p_3 ... p_k
k
1s
在位置 p_1,p_2,...,p_k
的位置。 2^k
数字将匹配模式(这就是您无法在 64
步中找到解决方案的原因):具有 1
或 {{1} 的数字} 在其他位置 0
和 p_i
。
要生成这些数字,请遍历 0
和 p
之间的所有数字 0
。对于每个2^(k-1)
:
- 以
p
开头。 - 根据所有
N = 0
检查p
,2^i
位于i
和0
之间。如果有匹配项(位置k-1
处的1
),将 2^{p_i} 添加到i
。 - 最后,您生成了一个与模式匹配的数字
N
。
这将在 N
步中生成与模式匹配的所有数字。
编辑一些想法
- 无论您选择哪种方法,
2^k * k
(64 位)和0
(模式本身)都会匹配模式。 - 第一种方法:
n
,模式本身,是匹配数字的上限。任何大于n
的数字都会在模式的n
上至少有一个1
(想想进位)。这个上限比0
更细。 - 第一种方法:您可以通过二等分找到
2^{i+1}
和i
,步数少于 64。 - 可以计算
j
并选择最有希望的方法。例如。不要对̀p_1 = i,... p_k = j
使用第一种方法。 - 第一种方法似乎比第二种方法慢,但它可以利用 branch prediction。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。