获取有序唯一项列表的最佳/最优雅Python方法

4 投票
2 回答
1513 浏览
提问于 2025-04-17 02:31

我有一个或多个不按顺序排列的对象序列,这些对象是不可变的并且可以被哈希,里面可能有重复的元素。我想要得到一个没有重复元素的排序序列。

现在我使用一个集合(set)来快速收集所有元素,这样可以自动去掉重复的,然后把它转换成列表,再进行排序:

result = set()
for s in sequences:
    result = result.union(s)
result = list(result)
result.sort()
return result

这样做是有效的,但我觉得这并不是“优雅”的做法。有没有更好的方法呢?

2 个回答

2

我觉得你的代码写得很好,简单明了,容易理解。

我们可以通过在 list() 后面连着写来稍微缩短一下:

result = set()
for s in sequences:
    result = result.union(s)
return sorted(result)

我其实不想再简化得太多,不过你可以用 reduce() 来实现:

result = reduce(lambda s, x: s.union(x), sequences, set())
return sorted(result)

个人觉得这个比上面的更难理解,但对那些熟悉函数式编程的人来说,可能会更喜欢。

补充:@agf 在这方面比我强多了。从下面的评论来看:

return sorted(reduce(set().union, sequences))

我完全没想到这样会有效。如果我理解得没错,我们给 reduce() 传了一个可调用的对象,这实际上是 set() 的一个实例的方法(为了讨论方便,叫它 x,但要注意我并不是说 Python 会把这个名字 x 和这个对象绑定在一起)。然后 reduce() 会把 sequences 中的前两个可迭代对象传给这个函数,返回 x,也就是我们正在使用的方法的实例。接着,reduce() 会不断调用 .union() 方法,要求它将 xsequences 中的下一个可迭代对象合并。由于 .union() 方法应该足够聪明,能意识到它正在和自己的实例合并,因此它不会做任何多余的工作,调用 x.union(x, some_iterable)x.union(some_iterable) 的速度应该是一样的。最后,reduce() 会返回 x,这样我们就得到了想要的集合。

对我个人来说,这有点复杂。我得仔细想一想才能理解,而 itertools.chain() 的解决方案我一下子就明白了。

补充:@agf 让这个变得简单了:

return sorted(reduce(set.union, sequences, set()))

这部分的理解要简单得多!如果我们再次用 x 来称呼 set() 返回的实例(就像上面说的,我并不是说 Python 会把这个名字 x 和这个实例绑定在一起);如果我们用 n 来指代 sequences 中的每一个“下一个”值;那么 reduce() 将会不断调用 set.union(x, n)。当然,这和 x.union(n) 是完全一样的。在我看来,如果你想要一个 reduce() 的解决方案,这个是最好的。

--

如果你想让它运行得快,问问自己:有没有办法把 itertools 应用到这个上?有一个不错的方法:

from itertools import chain
return sorted(set(chain(*sequences)))

*sequences 调用 itertools.chain() 可以把多个列表“扁平化”为一个可迭代对象。这有点复杂,但其实不算太难,而且这是一个常见的用法。

补充:正如 @Jbernardo 在最受欢迎的回答中所写的,@agf 在评论中也提到,itertools.chain() 返回的对象有一个 .from_iterable() 方法,文档上说它是懒加载的。* 符号会强制构建一个列表,如果可迭代对象很长,可能会消耗大量内存。实际上,你可能会有一个永无止境的生成器,而用 itertools.chain().from_iterable() 你可以一直从中提取值,只要你的程序在运行,而 * 符号则可能会导致内存耗尽。

正如 @Jbernardo 所说:

sorted(set(itertools.chain.from_iterable(sequences)))

这是最好的答案,我已经给它点赞了。

13

这个应该可以用:

sorted(set(itertools.chain.from_iterable(sequences)))

撰写回答