基于集合三元组的高效匹配算法

时间:2010-01-04 20:34:11

标签: java performance algorithm data-structures scalability

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

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

X | Y | Z

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

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中的前两项匹配。第三项不匹配,因为X未在X集中出现,Z1不会出现在Z集中。< / p>

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

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

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

6 个答案:

答案 0 :(得分:3)

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

一种简单的技术是从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])

答案 1 :(得分:1)

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

答案 2 :(得分:1)

你可以用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个部分构建此索引,然后将搜索每个树的结果与AND一起构建。

答案 3 :(得分:1)

使用单次传递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))。

答案 4 :(得分:0)

如何将HashSet(或HashSet s)用于列表2 ?这样,您只需要遍历列表1

答案 5 :(得分:0)

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

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

但要检查这个“手写”并不难。