【问题标题】:Python calculate co-occurrence of tuples in list of lists of tuplesPython计算元组列表中元组的共现
【发布时间】:2019-07-23 16:46:53
【问题描述】:

我有一个很大的元组列表,比如

actions  = [ [('d', 'r'), ... ('c', 'e'),('', 'e')],
             [('r', 'e'), ... ('c', 'e'),('d', 'r')],
                                    ... , 
             [('a', 'b'), ... ('c', 'e'),('c', 'h')]
           ]

我想找到元组的共现。

我已经尝试了this question 的建议,但接受的答案太慢了。例如,在具有 1494 个元组列表的列表中,生成的字典大小为 18225703,并且需要数小时才能运行 2 个元组共现。所以简单的排列和计数似乎不是答案,因为我有一个更大的列表。

我希望输出能够在一定程度上提取最常见的对 (2) 或更多(最多 3,4,5)个同时出现最多的元组。以前面的列表为例:

('c', 'e'),('d', 'r') 

在搜索对时会共同出现,因为它们经常同时出现。有没有一种有效的方法来实现这一点?

【问题讨论】:

  • 您是否使用itertools 模块尝试过其他答案?
  • 这个答案看起来相当干净高效:stackoverflow.com/a/49079618/1467943
  • 你试过Counter,即Counter(x for i in actions for x in i)吗?
  • @AlexandreB。是的,我实现的解决方案是使用 itertools 来提取唯一元组,但同样需要很长时间,它会找到 6038 个唯一元组,然后我创建它们之间的排列,然后计算出现次数,从而产生一个巨大的字典/稀疏矩阵。
  • 同现是什么意思?

标签: python list dictionary tuples find-occurrences


【解决方案1】:

我认为更快的算法是没有希望的:你必须计算组合来计算它们。但是,如果存在您不感兴趣的共现阈值,则可以降低算法的复杂性。在这两种情况下,都希望降低空间复杂度。

我们举个小例子:

>>> 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 次共现,依此类推。

如果这仍然很慢,请切换到更快的语言。

【讨论】:

    猜你喜欢
    • 2017-07-26
    • 1970-01-01
    • 1970-01-01
    • 2013-04-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多