【问题标题】:Efficient Matching Algorithm for Set Based Triplets基于集合的三元组的高效匹配算法
【发布时间】:2011-01-01 09:41:39
【问题描述】:

我正在寻找解决以下问题的有效方法。

列表 1 是由原始三元组标识的记录列表:

X | Y | Z

列表 2 是由三个集合标识的记录列表。一个 Xs,一个 Ys,一个 Zs。 X、Y、Z 与列表一中的“类型”相同,因此可以直接相互比较。

Set(X) | Set(Y) | Set(Z)

对于列表 1 中的项目,我需要找到列表 2 中的所有项目,其中列表 1 中的 X、Y、Z 都出现在列表 2 中的相应集合中。这可以通过一个示例得到最好的证明:

清单1:

X1, Y1, Z1

清单 2:

(X1, X2) | (Y1) | (Z1, Z3)

(X1) | (Y1, Y2) | (Z1, Z2, Z3)

(X3) | (Y1, Y3) | (Z2, Z3)

在上面,列表 1 中的项目将匹配列表 2 中的前两项。第三项将不匹配,因为 X1 没有出现在 X 集中,Z1 没有出现在 Z 集中。

我已经编写了一个功能正确的算法版本,但我担心在更大数据集上的性能。两个列表都非常大,因此迭代列表 1,然后对每个项目执行列表 2 的迭代将非常低效。

我尝试通过将列表 2 中的每个项目反规范化为映射来构建索引,但每个项目的索引中的索引条目数与项目子集的大小成正比。因此,这会使用非常高的内存,并且还需要一些重要的资源来构建。

任何人都可以向我建议解决此问题的最佳方法。我很乐意同时考虑内存和 CPU 的最佳解决方案,但取得平衡会很好!

【问题讨论】:

  • 列表 2 的集合中的项目是否有任何排序? (即,这些项目对它们有逻辑顺序吗?)
  • 天啊,我被符号弄糊涂了。通常为“|”表示“或”而不是“”,并且 (X1, X2) 表示 2 个元素的元组,而不是 X1 | X2。阅读所有这些东西,我的脑袋都在旋转。
  • 每个集合中元素的典型数量是多少?像你的例子中只有几个,还是可以有数百个?
  • 你真的应该提供更多细节。清单 1 和清单 2 的大致尺寸是多少?清单 2 中的每个集合中大约有多少个值?这些集合中有什么样的值,整数?列表 2 中的值是否被大量重用,或者它们几乎都是唯一的(只有几个唯一值,数千个唯一值)?
  • @Dav : 这些项目在列表 2 的集合中没有任何排序。

标签: java performance algorithm data-structures scalability


【解决方案1】:

有很多方法可以解决这个问题。哪个正确取决于数据和可用内存量。

一种简单的技术是从 list2 构建一个表,以加速来自 list1 的查询。

from collections import defaultdict

# Build "hits".  hits[0] is a table of, for each x,
# which items in list2 contain it. Likewise hits[1]
# is for y and hits[2] is for z.
hits = [defaultdict(set) for i in range(3)]
for rowid, row in enumerate(list2):
    for i in range(3):
        for v in row[i]:
            hits[i][v].add(rowid)

# For each row, query the database to find which
# items in list2 contain all three values.
for x, y, z in list1:
    print hits[0][x].intersection(hits[1][y], hits[2][z])

【讨论】:

  • 谢谢 Jason,这让我对这个问题的看法完全不同。我被困在“一个索引”的思路中,但创建三个要灵活得多。
【解决方案2】:

如果集合的总大小不是太大,您可以尝试将列表 2 建模为位域。不过,该结构可能会非常分散 - 也许在 Bit arrays 上的 Wikipedia 文章中引用的结构(朱迪数组、尝试、布隆过滤器)可以帮助解决规范化方法的内存问题。

【讨论】:

    【解决方案3】:

    你可以用 List2 构建一棵树;树的第一级是出现在集合 X 中的 (X1..Xn) 中的第一级。第二级是第二项的值,加上一个叶节点,其中包含仅包含 X1 的列表集。下一级包含下一个可能的值,依此类推。

    Root --+--X1--+--EOF--> List of pointers to list2 lines containing only "X1"
           |      |
           |      +--X2---+--EOF--> List of pointers to list2 lines containing only "X1,X2"
           |      |       |
           |      |       +--X3--+--etc--
           |      |       
           |      +--X3---+--EOF--> "X1,X3"
           |             
           +--X2--+--EOF--> "X2"
           |      |
           |      +--X3---+--EOF--> "X2,X3"
           |      |       |
           ...
    

    这在内存消耗方面很昂贵(我认为是 N^2 log K?其中 N=X 的值,K=List2 中的行),但会导致快速检索。如果可能的 X 数量很大,那么这种方法将失效...

    显然,您可以为元组的所有 3 个部分构建此索引,然后将搜索每棵树的结果与在一起。

    【讨论】:

      【解决方案4】:

      单次遍历list2 是一种相当有效的方法。首先为 list1 中的项目建立索引。

      from collections import defaultdict
      
      # index is HashMap<X, HashMap<Y, HashMap<Z, Integer>>>
      index = defaultdict(lambda: defaultdict(dict))
      for rowid, (x, y, z) in enumerate(list1):
          index[x][y][z] = rowid
      
      for rowid2, (xs, ys, zs) in enumerate(list2):
          xhits = defaultdict(list)
          for x in xs:
              if x in index:
                  for y, zmap in index[x].iteritems():
                      xhits[y].append(zmap)
      
          yhits = defaultdict(list)
          for y in ys:
              if y in xhits:
                  for z, rowid1 in xhits[y].iteritems():
                      yhits[z].append(rowid1)
      
          for z in zs:
              if z in yhits:
                  for rowid1 in yhits[z]:
                      print "list1[%d] matches list2[%d]" % (hit[z], rowid2)
      

      这里的额外簿记可能使其比索引 list2 慢。但是由于在您的情况下 list1 通常比 list2 小得多,因此这将使用更少的内存。如果您正在从磁盘读取 list2,则使用此算法您无需将其任何部分保存在内存中。

      内存访问可能很重要,所以我不能确定在实践中哪个会更快。必须测量。在这两种情况下,最坏情况的时间复杂度是 O(len(list1)*len(list2))。

      【讨论】:

        【解决方案5】:

        List 2 使用HashSet(或HashSets)怎么样?这样您只需要遍历 List 1

        【讨论】:

        • 如果列表 1 和列表 2 中的项目可以直接比较,这将起作用。但是,我们需要针对列表 1 中的每个出现测试列表 2 中的每个项目,因为这些项目不能直接比较,并且列表 2 是驱动程序。
        • 在 List2 包含基元集和 List1 包含基元的意义上不具有可比性?在这种情况下,将 List2 转换为单个 HashSet(例如 {X1, X2, Y1, Z1, Z3} )并继续迭代 List1 并为每个成员在 hashset 上快速执行 contains() 。创建哈希集不是免费的,但您可以考虑权衡。
        • 大卫,我明白你的意思,这可能会奏效。我应该澄清一下,X、Y、Zs 实际上只是整数集,因此在 X 和 Y 集中可能具有相同的值,但这种区别会在单个哈希集中丢失。我很抱歉。
        【解决方案6】:

        如果您使用Guava,则有一种高级方法可以做到这一点,它不一定是最佳,但不会做任何疯狂的事情:

        List<SomeType> list1 = ...;
        List<Set<SomeType>> candidateFromList2 = ...;
        if (Sets.cartesianProduct(candidateFromList2).contains(list1)) { ... }
        

        但是检查这个“速写”也不是那么难。

        【讨论】:

          猜你喜欢
          • 2017-09-15
          • 1970-01-01
          • 2011-07-05
          • 1970-01-01
          • 1970-01-01
          • 2020-10-30
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多