减去集合列表

时间:2016-01-29 20:18:31

标签: python algorithm list set list-comprehension

给出一组集合:

allsets = [set([1, 2, 4]), set([4, 5, 6]), set([4, 5, 7])]

什么是pythonic方法来计算与其他集没有重叠的元素集的相应列表?

only = [set([1, 2]), set([6]), set([7])]

有没有办法用列表理解来做到这一点?

4 个答案:

答案 0 :(得分:19)

为避免二次运行时间,您需要进行初始传递以确定哪些元素出现在多个集合中:

import itertools
import collections
element_counts = collections.Counter(itertools.chain.from_iterable(allsets))

然后,您只需创建一个集合列表,保留仅出现一次的所有元素:

nondupes = [{elem for elem in original if element_counts[elem] == 1}
            for original in allsets]

或者,不是直接从nondupes构建element_counts,而是可以进行额外的传递来构建一组恰好出现在一个输入中的所有元素。这需要一个额外的声明,但它允许我们利用&运算符来设置交集,以使列表理解更短,更有效:

element_counts = collections.Counter(itertools.chain.from_iterable(allsets))
all_uniques = {elem for elem, count in element_counts.items() if count == 1}
#                                                     ^ viewitems() in Python 2.7
nondupes = [original & all_uniques for original in allsets]

时间似乎表明使用all_uniques集合可以为整个重复消除过程带来显着的加速。对于大量重复的输入集,在Python 3上大约有3.5x speedup,但对于Python 2上的整个重复消除过程只有大约30% speedup,因为更多的运行时由构造计数器。这种加速是相当可观的,但并不像通过首先使用element_counts来避免二次运行时那么重要。如果您使用的是Python 2并且此代码对速度至关重要,那么您需要使用普通的dictcollections.defaultdict代替Counter

另一种方法是从dupes构建element_counts集,并在列表解析中使用original - dupes而不是original & all_uniques,如munk suggested。这比使用all_uniques集和&表现更好还是更差取决于输入中的重复程度以及您使用的Python版本,但它doesn't {{3无论如何都要产生很大的不同。

答案 1 :(得分:7)

是的,它可以完成,但几乎不是pythonic

>>> [(i-set.union(*[j for j in allsets if j!= i])) for i in allsets]   
[set([1, 2]), set([6]), set([7])]

可以找到关于集合的一些参考in the documentation*运算符名为unpacking operator

答案 2 :(得分:6)

使用计数器和理解的稍微不同的解决方案,以利用-运算符来设置差异。

from itertools import chain
from collections import Counter

allsets = [{1, 2, 4}, {4, 5, 6}, {4, 5, 7}]
element_counts = Counter(chain.from_iterable(allsets))

dupes = {key for key in element_counts 
         if element_counts[key] > 1}

only = [s - dupes for s in allsets]

答案 3 :(得分:2)

itertools.chain的另一种解决方案:

>>> from itertools import chain
>>> [x - set(chain(*(y for y in allsets if y!=x))) for x in allsets]
[set([1, 2]), set([6]), set([7])]

如果没有解压缩并使用chain.from_iterable,也可以这样做。