我正在寻找解决以下问题的有效方法。
列表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最佳解决方案,但取得平衡将会很好!
答案 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)) { ... }
但要检查这个“手写”并不难。