假设我有一些 64 位整数的模式。给定一个 64 位整数 n
如何有效地列出所有匹配 n
的模式?
更准确地说,我对这组感兴趣: {pi 在 (n & pi) == pi}
等模式中感谢您的帮助
答案 0 :(得分:1)
一种优化的解决方案,复杂度不是 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, 100, 101],
3: [001, 010, 011],
4: [0001, 0010, 0011, 0100, 0101, 0110, 0111]
}
n = 1 <=> b001
第 2 个和第 3 个 LSB 为零
=> [001, 010, 011, 100, 101, 110, 111], [001, 100, 101]
和 [001, 010, 011]
的交集
=> [001]
n = 2 <=> b010
第一个和第三个 LSB 为零
=>[001, 010, 011, 100, 101, 110, 111], [010, 100, 110] 和 [001, 010, 011] 的交点
=> [010]
n = 3 <=> b011
第三个 LSB 为零
=> [001, 010, 011, 100, 101, 110, 111] 和 [001, 010, 011] 的交集
=> [001, 010, 011]
n = 4 <=> b100
第一个和第二个 LSB 为零
=> [001, 010, 011, 100, 101, 110, 111], [010, 100, 110] 和 [001, 100, 101] 的交集
=> [100]
n = 5 <=> b101
第二个 LSB 为零
=> [001, 010, 011, 100, 101, 110, 111] 和 [001, 100, 101] 的交集
=> [001, 100, 101]
n = 6 <=> b110
第一个 LSB 为零
=>[001, 010, 011, 100, 101, 110, 111] 和 [010, 100, 110] 的交点
=>[010, 100, 110]
n = 7 => b111
第 4 个 LSB 为零
=> [0001, 0010, 0011, 0100, 0101, 0110, 0111]
答案 1 :(得分:0)
如果我正确理解了问题(希望了解更多相关细节)。您可以将 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])
答案 2 :(得分:0)
幸运的是,您的支票 (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
)处理问题可能会更快。
答案 3 :(得分:0)
最简单的方法是根据模式 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
where k > t.2^j
具有形式(欧几里德除法):(p + q.2^j) + r
where r < 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_2, ... p_k = j
使用第一种方法。