获取所有匹配位掩码的有效方法?

时间:2021-01-01 21:59:48

标签: c++ algorithm data-structures bit

假设我有一些 64 位整数的模式。给定一个 64 位整数 n 如何有效地列出所有匹配 n 的模式?

更准确地说,我对这组感兴趣: {pi 在 (n & pi) == pi}

等模式中

感谢您的帮助

4 个答案:

答案 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 CommentPagebroadcasting 一起用于您的任务。

编辑:我的解决方案是在 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 ▷ p1p1 ▷ 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 !▷ p2p1 ▷ p2 跟随 n !▷ p1)处理问题可能会更快。

答案 3 :(得分:0)

最简单的方法是根据模式 0 检查所有数字,从 2^64-1n。您需要 2^64 步(在 C++ 中,查看矢量化以减少步数)。

没有任何方法可以在 64 步(见下文)中为您提供相同的结果,但您可以改进朴素的方法。

第一个想法:假设您的模式是:

bit     0 0 ...  0 1 ? ? ? ? ? 1 0 ... 0
pos     0 1 ...    i  ...      j  ... 63

其中 i 是第一个 1j 是最后一个 1。您可以在 64 个步骤中找到 ij(检查 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} 的数字} 在其他位置 0p_i

要生成这些数字,请遍历 0p 之间的所有数字 0。对于每个2^(k-1)

  • p 开头。
  • 根据所有 N = 0 检查 p2^i 位于 i0 之间。如果有匹配项(位置 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使用第一种方法。
  • 第一种方法似乎比第二种方法慢,但它可以利用 branch prediction