【问题标题】:Subtraction over a list of sets对集合列表进行减法
【发布时间】:2016-05-07 16:57:38
【问题描述】:

给定一个集合列表:

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

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

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

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

【问题讨论】:

标签: python algorithm list set list-comprehension


【解决方案1】:

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

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]

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

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

另一种方法是从element_counts 构造一个dupes 集合,并在列表理解中使用original - dupes 而不是original & all_uniques,就像munk 的suggested。这比使用all_uniques 集合和& 执行得更好还是更差将取决于您输入的重复程度以及您使用的Python 版本,但它doesn't seem 会产生很大的不同无论哪种方式。

【讨论】:

  • 当然是更好的方法。 OP 的一些链接 1.chain.from_iterable 2.collections.Counter
  • 使用 [{elem for elem in original...}] 的文字语法可能会更好一些
  • @munk:哦,对了。我总是忘记使用集合文字和集合推导。
  • 与唯一元素相交比在我的真实世界数据集中减去重复项快约 6 倍。在我的数据集中,独特的元素很少,重复的元素很多。
【解决方案2】:

使用计数器和推导式的稍微不同的解决方案,以利用 - 运算符的集合差异。

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]

【讨论】:

  • 我实际上在发布我的原始解决方案后考虑过这一点,尽管我使用了 & 并制作了 unique_elements 集而不是 dupes 集。 Timing 显示 & 比每次运行 Python 级别的集合理解要快 30%。 &- 性能是否更好可能取决于元素重复的程度以及您使用的 Python 版本。
  • 选择此解决方案作为最佳答案,因为 1) 它非常易读,2) 在我的真实数据上比 user2357112 解决方案快 15%-30%
  • 非常好的和可读的解决方案。我最初选择这个作为基于可读性和速度的最佳答案。后来改成user2357112的答案,经过进一步测试,速度明显更快。
【解决方案3】:

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

【讨论】:

    【解决方案4】:

    是的,它可以做到,但几乎不是 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

    【讨论】:

    • eww 同意了。像瘟疫一样避免这种情况。更喜欢一些冗长的 for 循环(但伟大的工作 Bhargav!)
    • 你不需要内部列表
    • @PadraicCunningham 你更喜欢那里的 genexp?
    猜你喜欢
    • 2012-02-02
    • 1970-01-01
    • 2017-08-14
    • 1970-01-01
    • 1970-01-01
    • 2011-02-10
    • 1970-01-01
    • 1970-01-01
    • 2015-05-27
    相关资源
    最近更新 更多