【问题标题】:Union of multiple sets in pythonpython中多个集合的并集
【发布时间】:2015-08-26 17:49:14
【问题描述】:
[[1, '34', '44'], [1, '40', '30', '41'], [1, '41', '40', '42'], [1, '42', '41', '43'], [1, '43', '42', '44'], [1, '44', '34', '43']]

我有一个列表列表。我的目标是检查任何一个子列表是否与其他子列表有任何共同点(不包括要比较的第一个索引对象)。如果它有任何共同点,那么统一这些子列表。

例如,对于这个例子,我的最终答案应该是这样的:

[[1, '34, '44', '40' '30', '41', '42', '43']]

我可以理解我应该将子列表转换为集合,然后使用 union() 和 intersection() 操作。但我坚持的是如何比较每个集合/子列表。我无法在列表上运行循环并一一比较每个子列表,因为列表的内容会被修改,这会导致错误。

我想知道是否有任何有效的方法来比较所有子列表(转换为集合)并获得它们的联合?

【问题讨论】:

  • 你需要同样的订单吗?
  • 不,不需要保留顺序。
  • 是否还有其他以 e.g. 开头的列表? 2,哪个不应该合并?
  • 其实我忘了强调一个重要条件。对不起,我错了。我还提到,当且仅当子列表有共同点时才应该统一子列表,否则它们应该保持原样。因此,首先需要检查 intersection() 如果不为空,则只需执行 union。 @Peter Wood 不,不会有任何带有单独起始索引元素的子列表,如“2”或“3”。我的意思是在列表列表中,所有子列表都将具有相同的第一个索引元素。

标签: python list python-3.x set-union


【解决方案1】:

itertools 模块解决了这个问题:

>>> from itertools import chain
>>> list(set(chain.from_iterable(d)))
[1, '41', '42', '43', '40', '34', '30', '44']

另一种方法是将列表解压缩为 union() 的单独参数:

>>> list(set().union(*d))
[1, '41', '42', '43', '40', '34', '30', '44']

后一种方式消除了所有重复项,并且不需要首先将输入转换为集合。此外,它不需要导入。

【讨论】:

  • itertools 通常如何扩展?根据您的经验,这种操作可以处理数千万或数亿个项目长列表(这里的“项目”是字符串)吗?甚至更大?
  • chain.from_iterable() 步骤是尺度不变的。在任何给定时间,它的整个状态都存储在两个指向迭代器的指针中。 set()list() 部分占用的内存与唯一输入的总数成比例。在我的 64 位机器上,一亿个唯一输入需要 4.3 GB 的 RAM 用于设置对象,0.9 GB 用于列表对象。
  • 你最好把set.union()写成set()用空集初始化。在这种情况下这没问题,但我花了很多时间寻找错误,因为我认为这可以概括为交集。使用set.,您可以同时进行联合和交叉!
  • @RadioControlled: set().union(*d) 与空的 d 一起使用,这是一个比对称性更重要的因素。
  • 太糟糕了,所有答案都在回答与所问问题不同的问题,但可能是由于问题中的示例选择不当。这个问题似乎实际上是在要求更接近超图连通分量算法的东西,而不仅仅是将所有东西都倾倒到一个集合中。 (dermen 的回答略有不同,但最终会更加错误。)
【解决方案2】:

使用unpacking operator *

>> list(set().union(*a))
[1, '44', '30', '42', '43', '40', '41', '34']

(感谢 Raymond Hettinger 和 ShadowRanger 的 cmets!)

(请注意

set.union(*tup)

将解压到

set.union(tup[0], tup[1], ... tup[n - 1])

)

【讨论】:

  • 这个方法很好用。谢谢。你能解释一下代码中'*'的用途吗?或者也许提供一个链接,我可以在其中学习与此相关的内容以了解更多信息。
  • FWIW,tuple 步骤无效,因为星形解包适用于任何可迭代对象。您还可以将列表理解替换为 map(set, a)。结果归结为list(set.union(*map(set, a)))
  • @TapojyotiMandal 请参阅答案中的解释。
  • 您可以通过将set.union(*map(set, a)) 更改为set().union(*a) 来显着减少临时sets 的数量。需要 map(set, 的唯一原因是因为您正在调用 set.union 并且第一个参数变成了它被调用的“自我”,但是如果您将空的 set 作为基础,union 接受任意剩余参数的迭代。
【解决方案3】:

您可以使用 itertools 来执行此操作。让我们假设您的列表有一个变量名 A

import itertools

single_list_with_all_values = list(itertools.chain(*A))
single_list_with_all_values.sort()

print set(single_list_with_all_values)

【讨论】:

  • 这个不错。虽然有一些改进。 1) chain(*it) 应始终更改为 chain.from_iterable(it)。 2) 不需要sort(),因为在进行set 时会丢失排序。 3) 如果没有排序,则在进行set 之前无需转换为list。随着这些变化,它归结为set(chain.from_iterable(d))
【解决方案4】:
In [20]: s
Out[20]: 
[[1, '34', '44'],
 [1, '40', '30', '41'],
 [1, '41', '40', '42'],
 [1, '42', '41', '43'],
 [1, '43', '42', '44'],
 [1, '44', '34', '43']]
In [31]: list({x for _list in s for x in _list})
Out[31]: [1, '44', '30', '42', '43', '40', '41', '34']

更新:

感谢cmets

【讨论】:

  • 你不需要列表推导,因为集合构造函数可以使用生成器。
  • @PeterWood OP 要求提供一份清单作为他的最终答案
  • 不,理解被传递给set。你不需要它。
  • 用集合推导替换列表推导可以将其归结为一个漂亮、干净的答案:list({x for _list in s for x in _list})
【解决方案5】:
>>> big = [[1, '34', '44'], [1, '40', '30', '41'], [1, '41', '40', '42'], [1, '42', '41', '43'], [1, '43', '42', '44'], [1, '44', '34', '43']]
>>> set(reduce ( lambda l,a : l + a, big))
set([1, '44', '30', '42', '43', '40', '41', '34'])

如果你真的想要一个列表作为最终结果

>>>>[list(set(reduce ( lambda l,a : l + a, big)))]
[[1, '44', '30', '42', '43', '40', '41', '34']]

如果您不喜欢为列表添加重新编码 lambda 函数:

>>>>[list(set(reduce ( list.__add__, big)))]
[[1, '44', '30', '42', '43', '40', '41', '34']]

编辑:在您建议使用 itertools.chain 而不是 list.__add__ 之后,我使用原始海报使用的原始变量为两者运行了 timeit。

似乎 timeit 时间 list.__add__ 大约 2.8 秒和 itertools.chain 大约 3.5 秒。

我在此页面上进行了检查,是的,您是对的,itertools.chain 包含一个 from_iterable 方法,该方法可大幅提升性能。请参阅下面的 list.__add__、itertools.chain 和 itertools.chain.from_iterable。

>>> timeit.timeit("[list(set(reduce ( list.__add__, big)))]", setup="big = [ [10,20,30,40] for ele in range(10000)]", number=30)
16.051744650801993
>>> timeit.timeit("[list(set(reduce ( itertools.chain, big)))]", setup="big = [ [10,20,30,40] for ele in range(10000)]", number=30)
54.721315866467194
>>> timeit.timeit("list(set(itertools.chain.from_iterable(big)))", setup="big = [ [10,20,30,40] for ele in range(10000)]", number=30)
0.040056066849501804

非常感谢您的建议:)

【讨论】:

  • 像这样将列表添加在一起是一个低效的 O(n**2) 操作,几乎总是一个坏主意。请改用itertools.chain
【解决方案6】:
from functools import reduce

out = list(reduce(set.union, iterable))

只要iterable 的至少第一个元素是一个集合。否则,

out = list(reduce(set.union, iterable[1:], set(iterable[0])))

【讨论】:

    【解决方案7】:

    仅用 python 2 测试:我个人喜欢 reduce 的可读性,搭配一个简单的条件函数,比如

    # PYTHON 2 ONLY!
    somelists = [[1, '41', '40', '42'], [1, '42', '41', '43'], [1, '43', '42', '44'], [1, '44', '34', '43']] # your original lists
    somesets = map(set,somelists) #your lists as sets
    
    def condition(s1,s2): # condition to apply recursively to the sets
        if s1.intersection(s2):
            return s1.union(s2)
    reduce( condition,somesets)
    #{1, '30', '34', '40', '41', '42', '43', '44'}
    

    当然,如果您愿意list([reduce(...,您可以将此结果转换为二维列表

    我会注意到这比chain.fromiterable 答案慢了 3 倍。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-10-28
      • 2018-09-24
      • 2019-02-11
      • 1970-01-01
      • 2015-10-30
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多