我认为更快的算法是没有希望的:你必须计算组合来计算它们。但是,如果存在您不感兴趣的共现阈值,则可以降低算法的复杂性。在这两种情况下,都希望降低空间复杂度。
我们举个小例子:
>>> actions = [[('d', 'r'), ('c', 'e'),('', 'e')],
... [('r', 'e'), ('c', 'e'),('d', 'r')],
... [('a', 'b'), ('c', 'e'),('c', 'h')]]
一般回答
This answer 可能是大型列表的最佳选择,但您可以避免创建中间列表。首先,在所有存在的元素对上创建一个可迭代对象(在您的情况下,元素也是对,但这没关系):
>>> import itertools
>>> it = itertools.chain.from_iterable(itertools.combinations(pair_list, 2) for pair_list in actions)
如果我们想看到结果,我们必须使用可迭代对象:
>>> list(it)
[(('d', 'r'), ('c', 'e')), (('d', 'r'), ('', 'e')), (('c', 'e'), ('', 'e')), (('r', 'e'), ('c', 'e')), (('r', 'e'), ('d', 'r')), (('c', 'e'), ('d', 'r')), (('a', 'b'), ('c', 'e')), (('a', 'b'), ('c', 'h')), (('c', 'e'), ('c', 'h'))]
然后计算排序的对(使用新的it!)
>>> it = itertools.chain.from_iterable(itertools.combinations(pair_list, 2) for pair_list in actions)
>>> from collections import Counter
>>> c = Counter((a,b) if a<=b else (b,a) for a,b in it)
>>> c
Counter({(('c', 'e'), ('d', 'r')): 2, (('', 'e'), ('d', 'r')): 1, (('', 'e'), ('c', 'e')): 1, (('c', 'e'), ('r', 'e')): 1, (('d', 'r'), ('r', 'e')): 1, (('a', 'b'), ('c', 'e')): 1, (('a', 'b'), ('c', 'h')): 1, (('c', 'e'), ('c', 'h')): 1})
>>> c.most_common(2)
[((('c', 'e'), ('d', 'r')), 2), ((('', 'e'), ('d', 'r')), 1)]
至少在空间方面,这个解决方案应该是高效的,因为一切都是惰性的,Counter 的元素数是同一列表中元素的组合数,最多为N(N-1)/2 其中@ 987654329@ 是所有列表中不同元素的数量(“最多”,因为有些元素永远不会“相遇”,因此永远不会发生某种组合)。
时间复杂度为O(M . L^2),其中M 是列表的数量,L 是最大列表的大小。
在同现次数上有一个阈值
我假设列表中的所有元素都是不同的。关键思想是如果一个元素只出现在一个列表中,那么这个元素绝对没有机会在这场比赛中击败任何人:它将与他的所有邻居共同出现1,和 0 与其他列表的元素。如果有很多“孤儿”,在处理计算组合之前删除它们可能会很有用:
>>> d = Counter(itertools.chain.from_iterable(actions))
>>> d
Counter({('c', 'e'): 3, ('d', 'r'): 2, ('', 'e'): 1, ('r', 'e'): 1, ('a', 'b'): 1, ('c', 'h'): 1})
>>> orphans = set(e for e, c in d.items() if c <= 1)
>>> orphans
{('a', 'b'), ('r', 'e'), ('c', 'h'), ('', 'e')}
现在,试试同样的算法:
>>> it = itertools.chain.from_iterable(itertools.combinations((p for p in pair_list if p not in orphans), 2) for pair_list in actions)
>>> c = Counter((a,b) if a<=b else (b,a) for a,b in it)
>>> c
Counter({(('c', 'e'), ('d', 'r')): 2})
注意理解:没有括号,只有括号。
如果您在 N 个元素的列表中有 K 个孤儿,则该列表的时间复杂度从 N(N-1)/2 下降到 (N-K)(N-K-1)/2,也就是说(如果我没记错的话!)K.(2N-K-1) 组合更少。
这可以概括为:如果一个元素出现在两个或更少的列表中,那么它最多与其他元素有 2 次共现,依此类推。
如果这仍然很慢,请切换到更快的语言。