如何从列表中删除重复项,同时保持顺序?使用集合删除重复项会破坏原始顺序。 是否有内置的或python的习语?
当前回答
消除序列中的重复值,但保留其余项的顺序。使用通用发电机功能。
# for hashable sequence
def remove_duplicates(items):
seen = set()
for item in items:
if item not in seen:
yield item
seen.add(item)
a = [1, 5, 2, 1, 9, 1, 5, 10]
list(remove_duplicates(a))
# [1, 5, 2, 9, 10]
# for unhashable sequence
def remove_duplicates(items, key=None):
seen = set()
for item in items:
val = item if key is None else key(item)
if val not in seen:
yield item
seen.add(val)
a = [ {'x': 1, 'y': 2}, {'x': 1, 'y': 3}, {'x': 1, 'y': 2}, {'x': 2, 'y': 4}]
list(remove_duplicates(a, key=lambda d: (d['x'],d['y'])))
# [{'x': 1, 'y': 2}, {'x': 1, 'y': 3}, {'x': 2, 'y': 4}]
其他回答
1. 这些解决方案很好…… 为了在保留秩序的同时删除重复项,本页其他地方提出了优秀的解决方案:
seen = set()
[x for x in seq if not (x in seen or seen.add(x))]
以及变化,例如:
seen = set()
[x for x in seq if x not in seen and not seen.add(x)]
确实很受欢迎,因为它们简单、极简,并部署了正确的哈希以获得最佳效率。关于这些方法的主要抱怨似乎是,将方法see .add(x)“返回”的不变量None用作逻辑表达式中的常量(因此是多余的/不必要的)值(只是为了它的副作用)是笨拙和/或令人困惑的。
2. …but they waste one hash lookup per iteration. Surprisingly, given the amount of discussion and debate on this topic, there is actually a significant improvement to the code that seems to have been overlooked. As shown, each "test-and-set" iteration requires two hash lookups: the first to test membership x not in seen and then again to actually add the value seen.add(x). Since the first operation guarantees that the second will always be successful, there is a wasteful duplication of effort here. And because the overall technique here is so efficient, the excess hash lookups will likely end up being the most expensive proportion of what little work remains.
3.相反,让布景完成它的工作吧! 注意,上面的例子只调用set。加上预见,这样做总是会导致集合成员的增加。集合本身永远没有机会拒绝副本;我们的代码片段实际上已经篡夺了这个角色。使用显式的两步测试和设置代码剥夺了set自身排除这些重复的核心能力。
4. 单哈希查找代码: 下面的版本将每次迭代的哈希查找次数减少了一半,从两次减少到只有一次。
seen = set()
[x for x in seq if len(seen) < len(seen.add(x) or seen)]
from itertools import groupby
[ key for key,_ in groupby(sortedList)]
这个列表甚至不需要排序,充分条件是相等的值被分组在一起。
编辑:我假设“保持顺序”意味着列表实际上是有序的。如果不是这样,那么MizardX的解决方案是正确的。
社区编辑:然而,这是“将重复的连续元素压缩为单个元素”的最优雅的方法。
你可以引用一个列表推导式,因为它是由符号'_[1]'构建的。例如,下面的函数通过引用列表推导式对元素列表进行惟一化,而不改变它们的顺序。
def unique(my_list):
return [x for x in my_list if x not in locals()['_[1]']]
演示:
l1 = [1, 2, 3, 4, 1, 2, 3, 4, 5]
l2 = [x for x in l1 if x not in locals()['_[1]']]
print l2
输出:
[1, 2, 3, 4, 5]
这将保持秩序并在O(n)时间内运行。基本上,这个想法是在任何发现副本的地方创建一个洞,并将其沉到底部。使用读写指针。每当发现一个重复项时,只有读指针前进,写指针停留在重复项上覆盖它。
def deduplicate(l):
count = {}
(read,write) = (0,0)
while read < len(l):
if l[read] in count:
read += 1
continue
count[l[read]] = True
l[write] = l[read]
read += 1
write += 1
return l[0:write]
这里有一些替代选项:http://www.peterbe.com/plog/uniqifiers-benchmark
最快的一个:
def f7(seq):
seen = set()
seen_add = seen.add
return [x for x in seq if not (x in seen or seen_add(x))]
为什么要赋值。添加到seen_add而不是只调用see . Add ?Python是一种动态语言,解析可见。每次迭代添加比解析一个局部变量代价更大。观察。Add可能会在迭代之间发生更改,而运行时还不够聪明,无法排除这种情况。为了安全起见,它必须每次检查对象。
如果您计划在同一个数据集上大量使用这个函数,那么使用一个有序集可能会更好:http://code.activestate.com/recipes/528878/
O(1)每次操作的插入、删除和成员检查。
(小额外注意:see .add()总是返回None,所以以上值只是作为一种尝试更新集合的方式,而不是逻辑测试的组成部分。)
推荐文章
- 把if-elif-else语句放在一行中?
- 我如何结合两个数据框架?
- 如何计数列表中唯一值的出现
- 为什么Pycharm的检查人员抱怨“d ={}”?
- 如何JSON序列化集?
- 在python中,年龄从出生日期开始
- 使用pip安装SciPy
- 在Python中,我应该如何测试变量是否为None, True或False
- 如何在Python中从毫秒创建datetime ?
- 检查字符串是否包含字符串列表中的元素
- 如何解窝(爆炸)在一个熊猫数据帧列,成多行
- 如何使用pip安装opencv ?
- 在pip冻结命令的输出中“pkg-resources==0.0.0”是什么
- 格式y轴为百分比
- 熊猫连接问题:列重叠但没有指定后缀