是否有内置的程序在保留顺序的同时从Python列表中删除重复项?我知道我可以使用集合来删除重复项,但这会破坏原始顺序。我也知道我可以这样滚动自己:
def uniq(input):
output = []
for x in input:
if x not in output:
output.append(x)
return output
但是如果可能的话,我想利用一个内置的或更Pythonic的习惯用法。
是否有内置的程序在保留顺序的同时从Python列表中删除重复项?我知道我可以使用集合来删除重复项,但这会破坏原始顺序。我也知道我可以这样滚动自己:
def uniq(input):
output = []
for x in input:
if x not in output:
output.append(x)
return output
但是如果可能的话,我想利用一个内置的或更Pythonic的习惯用法。
Answers:
在这里,您有一些选择: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
给seen_add
而不是仅打电话给seen.add
?Python是一种动态语言,与解决seen.add
局部变量相比,解决每次迭代的成本更高。seen.add
可能在两次迭代之间发生了变化,并且运行时不够智能,无法排除这种情况。为了安全起见,它必须每次检查对象。
如果您打算在同一数据集上大量使用此功能,则最好使用有序集:http : //code.activestate.com/recipes/528878/
O(1)每个操作的插入,删除和成员检查。
(小的附加说明:seen.add()
始终返回None
,因此or
以上内容仅是尝试进行集合更新的一种方法,而不是逻辑测试的组成部分。)
seen_add
是一种改进,但时间可能会受到当时系统资源的影响。希望看到完整的时间安排
seen_add = seen.add
速度只会提高1%。这几乎没有意义。
编辑2016
正如Raymond所指出的那样,在OrderedDict
C语言中实现的python 3.5+中,列表理解方法将比OrderedDict
(除非您实际上需要列表的末尾-甚至在输入非常短的情况下)慢一些。因此,针对3.5+的最佳解决方案是OrderedDict
。
重要编辑2015
如@abarnert所述,more_itertools
库(pip install more_itertools
)包含一个unique_everseen
为解决此问题而构建的函数,列表理解中没有任何不可读的(not seen.add
)突变。这也是最快的解决方案:
>>> from more_itertools import unique_everseen
>>> items = [1, 2, 0, 1, 3, 2]
>>> list(unique_everseen(items))
[1, 2, 0, 3]
只需导入一个简单的库,就不会有黑客入侵。这来自itertools配方的实现,unique_everseen
如下所示:
def unique_everseen(iterable, key=None):
"List unique elements, preserving order. Remember all elements ever seen."
# unique_everseen('AAAABBBCCDAABBB') --> A B C D
# unique_everseen('ABBCcAD', str.lower) --> A B C D
seen = set()
seen_add = seen.add
if key is None:
for element in filterfalse(seen.__contains__, iterable):
seen_add(element)
yield element
else:
for element in iterable:
k = key(element)
if k not in seen:
seen_add(k)
yield element
在Python中2.7+
,这种用法是公认的通用用法(它可以工作,但没有针对速度进行优化,我现在将使用unique_everseen
)collections.OrderedDict
:
运行时间:O(N)
>>> from collections import OrderedDict
>>> items = [1, 2, 0, 1, 3, 2]
>>> list(OrderedDict.fromkeys(items))
[1, 2, 0, 3]
看起来比:
seen = set()
[x for x in seq if x not in seen and not seen.add(x)]
并且没有利用丑陋的hack:
not seen.add(x)
这取决于set.add
始终返回的就地方法的事实,None
因此not None
求值为True
。
但是请注意,尽管破解解决方案具有相同的运行时复杂度O(N),但其原始速度更快。
[seen.add(x) for x in seq if x not in seen]
,或者如果您不喜欢理解副作用,则使用for
循环:(for x in seq: seen.add(x) if x not in seen else None
仍然是单线,尽管在这种情况下,我认为单线是尝试在单线中具有的愚蠢特性。解决方案
seen = set(seq)
。
在Python 2.7中,从迭代器中删除重复项并同时保持其原始顺序的新方法是:
>>> from collections import OrderedDict
>>> list(OrderedDict.fromkeys('abracadabra'))
['a', 'b', 'r', 'c', 'd']
在Python 3.5中,OrderedDict具有C实现。我的时间表明,这是Python 3.5各种方法中最快也是最短的。
在Python 3.6中,常规字典变得有序且紧凑。(此功能适用于CPython和PyPy,但在其他实现中可能不存在)。这为我们提供了一种在保留订单的同时进行重复数据删除的最快方法:
>>> list(dict.fromkeys('abracadabra'))
['a', 'b', 'r', 'c', 'd']
在Python 3.7中,保证常规dict在所有实现中都排序。 因此,最短,最快的解决方案是:
>>> list(dict.fromkeys('abracadabra'))
['a', 'b', 'r', 'c', 'd']
对@max的响应:一旦移至3.6或3.7并使用常规dict而不是OrderedDict,就无法真正以其他任何方式击败性能。该词典非常密集,几乎可以无开销地转换为列表。目标列表的大小预先设置为len(d),这样可以保存列表推导中发生的所有调整大小。同样,由于内部键列表很密集,因此指针的复制几乎快如列表副本。
OrderedDict
为列表,它就会比我的机器上的其他任何方法(python 3.5)都快。如果确实需要将其转换为列表,则对于较小的输入,列表理解方法仍可以快1.5倍。也就是说,此解决方案更加清洁。
set()
将帮助更多的天真用户开发可再现的代码。
sequence = ['1', '2', '3', '3', '6', '4', '5', '6']
unique = []
[unique.append(item) for item in sequence if item not in unique]
独特→ ['1', '2', '3', '6', '4', '5']
n^2
None
在过程中建立另一个引用列表!)
for
循环
from itertools import groupby
[ key for key,_ in groupby(sortedList)]
该列表甚至不必排序,充分的条件是将相等的值分组在一起。
编辑:我认为“保留顺序”意味着该列表实际上是有序的。如果不是这种情况,那么MizardX的解决方案就是正确的解决方案。
社区编辑:但是,这是“将重复的连续元素压缩为单个元素”的最优雅的方法。
我想如果您想维持订单,
list1 = ['b','c','d','b','c','a','a']
list2 = list(set(list1))
list2.sort(key=list1.index)
print list2
list1 = ['b','c','d','b','c','a','a']
list2 = sorted(set(list1),key=list1.index)
print list2
list1 = ['b','c','d','b','c','a','a']
list2 = []
for i in list1:
if not i in list2:
list2.append(i)`
print list2
list1 = ['b','c','d','b','c','a','a']
list2 = []
[list2.append(i) for i in list1 if not i in list2]
print list2
对于另一个非常老的问题的另一个非常晚的答案:
该itertools
食谱有做到这一点,利用函数seen
集技术,但是:
key
功能。seen.add
而不是查找N次来优化循环。(f7
也可以这样做,但某些版本则不能。)ifilterfalse
,因此您只需要遍历Python中的唯一元素,而不是所有元素。(ifilterfalse
当然,您仍然可以在内部遍历所有这些对象,但这是在C中,而且速度更快。)实际上比它快f7
吗?这取决于您的数据,因此您必须对其进行测试并查看。如果您最后想要一个列表,请f7
使用listcomp,在这里没有办法做到这一点。(您可以直接append
代替yield
ing,也可以将生成器输入到list
函数中,但没有一个可以比listcomp内的LIST_APPEND快。)无论如何,通常挤出几微秒的时间不会像之所以重要,是因为它具有易于理解,可重用,已编写的功能,在您要装饰时不需要DSU。
与所有食谱一样,它也可以在中找到more-iterools
。
如果只希望没有key
条件,可以将其简化为:
def unique(iterable):
seen = set()
seen_add = seen.add
for element in itertools.ifilterfalse(seen.__contains__, iterable):
seen_add(element)
yield element
more-itertools
这显然是最好的答案。一个简单from more_itertools import unique_everseen
list(unique_everseen(items))
的方法比我的方法快得多,比公认的答案要好得多,我认为下载该库是值得的。我要社会的维基我的回答并添加此英寸
刚添加另一个(非常高性能的)实现的这样的功能从外部模块1:iteration_utilities.unique_everseen
:
>>> from iteration_utilities import unique_everseen
>>> lst = [1,1,1,2,3,2,2,2,1,3,4]
>>> list(unique_everseen(lst))
[1, 2, 3, 4]
我做了一些计时(Python的3.6),这些表明,它比我测试的所有其他办法,包括更快OrderedDict.fromkeys
,f7
并且more_itertools.unique_everseen
:
%matplotlib notebook
from iteration_utilities import unique_everseen
from collections import OrderedDict
from more_itertools import unique_everseen as mi_unique_everseen
def f7(seq):
seen = set()
seen_add = seen.add
return [x for x in seq if not (x in seen or seen_add(x))]
def iteration_utilities_unique_everseen(seq):
return list(unique_everseen(seq))
def more_itertools_unique_everseen(seq):
return list(mi_unique_everseen(seq))
def odict(seq):
return list(OrderedDict.fromkeys(seq))
from simple_benchmark import benchmark
b = benchmark([f7, iteration_utilities_unique_everseen, more_itertools_unique_everseen, odict],
{2**i: list(range(2**i)) for i in range(1, 20)},
'list size (no duplicates)')
b.plot()
并确保我也进行了更多重复的测试,以检查是否有所不同:
import random
b = benchmark([f7, iteration_utilities_unique_everseen, more_itertools_unique_everseen, odict],
{2**i: [random.randint(0, 2**(i-1)) for _ in range(2**i)] for i in range(1, 20)},
'list size (lots of duplicates)')
b.plot()
还有一个仅包含一个值:
b = benchmark([f7, iteration_utilities_unique_everseen, more_itertools_unique_everseen, odict],
{2**i: [1]*(2**i) for i in range(1, 20)},
'list size (only duplicates)')
b.plot()
在所有这些情况下,该iteration_utilities.unique_everseen
功能都是最快的(在我的计算机上)。
此iteration_utilities.unique_everseen
函数还可以处理输入中不可散列的值(但是使用O(n*n)
性能而不是O(n)
可散列值时的性能)。
>>> lst = [{1}, {1}, {2}, {1}, {3}]
>>> list(unique_everseen(lst))
[{1}, {2}, {3}]
1免责声明:我是该软件包的作者。
seen_add = seen.add
-基准测试需要吗?
dict.fromkeys()
方法添加到图表中吗?
ordereddict.fromkeys
?
借用在定义Haskell nub
函数的列表时使用的递归思想,这将是一种递归方法:
def unique(lst):
return [] if lst==[] else [lst[0]] + unique(filter(lambda x: x!= lst[0], lst[1:]))
例如:
In [118]: unique([1,5,1,1,4,3,4])
Out[118]: [1, 5, 4, 3]
我尝试使用它来增加数据量,并看到了次线性时间复杂度(不确定,但建议对于正常数据应该很好)。
In [122]: %timeit unique(np.random.randint(5, size=(1)))
10000 loops, best of 3: 25.3 us per loop
In [123]: %timeit unique(np.random.randint(5, size=(10)))
10000 loops, best of 3: 42.9 us per loop
In [124]: %timeit unique(np.random.randint(5, size=(100)))
10000 loops, best of 3: 132 us per loop
In [125]: %timeit unique(np.random.randint(5, size=(1000)))
1000 loops, best of 3: 1.05 ms per loop
In [126]: %timeit unique(np.random.randint(5, size=(10000)))
100 loops, best of 3: 11 ms per loop
我也认为很有趣的是,其他操作可以很容易地将其推广到唯一性。像这样:
import operator
def unique(lst, cmp_op=operator.ne):
return [] if lst==[] else [lst[0]] + unique(filter(lambda x: cmp_op(x, lst[0]), lst[1:]), cmp_op)
例如,您可以传入一个函数,该函数使用舍入的概念将其舍入为相同的整数,就好像它是“相等”是出于唯一性目的一样,如下所示:
def test_round(x,y):
return round(x) != round(y)
然后unique(some_list,test_round)将提供列表的唯一元素,其中唯一性不再意味着传统的相等性(这是通过使用任何基于集合或基于dict-key的方法来解决的),而是意味着对于每个可能舍入到的整数K,只有第一个舍入到K的元素,例如:
In [6]: unique([1.2, 5, 1.9, 1.1, 4.2, 3, 4.8], test_round)
Out[6]: [1.2, 5, 1.9, 4.2, 3]
filter
都几乎不会从上一个调用中受益。但是,如果相对于数组大小,唯一元素的数量较小,则这应该表现得很好。
5倍速减少变体但更复杂
>>> l = [5, 6, 6, 1, 1, 2, 2, 3, 4]
>>> reduce(lambda r, v: v in r[1] and r or (r[0].append(v) or r[1].add(v)) or r, l, ([], set()))[0]
[5, 6, 1, 2, 3, 4]
说明:
default = (list(), set())
# use list to keep order
# use set to make lookup faster
def reducer(result, item):
if item not in result[1]:
result[0].append(item)
result[1].add(item)
return result
>>> reduce(reducer, l, default)[0]
[5, 6, 1, 2, 3, 4]
您可以引用列表理解,因为它是由符号“ _ [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]
MizardX的答案很好地总结了多种方法。
这是我在大声思考时想到的:
mylist = [x for i,x in enumerate(mylist) if x not in mylist[i+1:]]
O(n)
操作,并且您在每个项目上都执行它,因此解决方案的最终复杂度为O(n^2)
。对于这样一个琐碎的问题,这是不可接受的。
您可以进行某种丑陋的列表理解技巧。
[l[i] for i in range(len(l)) if l.index(l[i]) == i]
i,e in enumerate(l)
给l[i] for i in range(len(l))
。
一个简单的递归解决方案:
def uniquefy_list(a):
return uniquefy_list(a[1:]) if a[0] in a[1:] else [a[0]]+uniquefy_list(a[1:]) if len(a)>1 else [a[0]]
消除序列中的重复值,但保留其余项目的顺序。使用通用发生器功能。
# 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}]
如果您需要一支班轮,那么这可能会有所帮助:
reduce(lambda x, y: x + y if y[0] not in x else x, map(lambda x: [x],lst))
...应该可以,但是如果我错了,请纠正我
如果您经常使用pandas
,并且美观优先于性能,那么请考虑内置功能pandas.Series.drop_duplicates
:
import pandas as pd
import numpy as np
uniquifier = lambda alist: pd.Series(alist).drop_duplicates().tolist()
# from the chosen answer
def f7(seq):
seen = set()
seen_add = seen.add
return [ x for x in seq if not (x in seen or seen_add(x))]
alist = np.random.randint(low=0, high=1000, size=10000).tolist()
print uniquifier(alist) == f7(alist) # True
定时:
In [104]: %timeit f7(alist)
1000 loops, best of 3: 1.3 ms per loop
In [110]: %timeit uniquifier(alist)
100 loops, best of 3: 4.39 ms per loop
不使用导入的模块或集的解决方案:
text = "ask not what your country can do for you ask what you can do for your country"
sentence = text.split(" ")
noduplicates = [(sentence[i]) for i in range (0,len(sentence)) if sentence[i] not in sentence[:i]]
print(noduplicates)
给出输出:
['ask', 'not', 'what', 'your', 'country', 'can', 'do', 'for', 'you']
此方法是二次方的,因为我们对列表的每个元素都有一个线性查找列表(由于存在以下原因,我们必须增加重新排列列表的成本) del
)。
就是说,如果我们从列表的末尾开始,朝着原点前进,删除子列表左侧的每个术语,就有可能进行适当的操作
代码中的这个想法很简单
for i in range(len(l)-1,0,-1):
if l[i] in l[:i]: del l[i]
实施的简单测试
In [91]: from random import randint, seed
In [92]: seed('20080808') ; l = [randint(1,6) for _ in range(12)] # Beijing Olympics
In [93]: for i in range(len(l)-1,0,-1):
...: print(l)
...: print(i, l[i], l[:i], end='')
...: if l[i] in l[:i]:
...: print( ': remove', l[i])
...: del l[i]
...: else:
...: print()
...: print(l)
[6, 5, 1, 4, 6, 1, 6, 2, 2, 4, 5, 2]
11 2 [6, 5, 1, 4, 6, 1, 6, 2, 2, 4, 5]: remove 2
[6, 5, 1, 4, 6, 1, 6, 2, 2, 4, 5]
10 5 [6, 5, 1, 4, 6, 1, 6, 2, 2, 4]: remove 5
[6, 5, 1, 4, 6, 1, 6, 2, 2, 4]
9 4 [6, 5, 1, 4, 6, 1, 6, 2, 2]: remove 4
[6, 5, 1, 4, 6, 1, 6, 2, 2]
8 2 [6, 5, 1, 4, 6, 1, 6, 2]: remove 2
[6, 5, 1, 4, 6, 1, 6, 2]
7 2 [6, 5, 1, 4, 6, 1, 6]
[6, 5, 1, 4, 6, 1, 6, 2]
6 6 [6, 5, 1, 4, 6, 1]: remove 6
[6, 5, 1, 4, 6, 1, 2]
5 1 [6, 5, 1, 4, 6]: remove 1
[6, 5, 1, 4, 6, 2]
4 6 [6, 5, 1, 4]: remove 6
[6, 5, 1, 4, 2]
3 4 [6, 5, 1]
[6, 5, 1, 4, 2]
2 1 [6, 5]
[6, 5, 1, 4, 2]
1 5 [6]
[6, 5, 1, 4, 2]
In [94]:
l[:] = <one of the the faster methods>
如果要进行就地操作,就可以使用,不是吗?
a=[1]; b=a; a[:]=[2]
那么b==[2]
值True
,我们可以说,我们正在做原地的,不过你的建议是使用新的空间,有一个新的名单是什么,用新的数据替换旧数据和标记用于垃圾收集的旧数据,因为它不再被任何东西引用,因此说它在原地运行有点延伸了概念,这已经证明了我的想法是可能的……效率低下吗?是的,但我已经事先告知了。
zmk的方法使用列表理解,它非常快,但自然保持顺序。对于区分大小写的字符串,可以轻松对其进行修改。这也保留了原始情况。
def DelDupes(aseq) :
seen = set()
return [x for x in aseq if (x.lower() not in seen) and (not seen.add(x.lower()))]
紧密相关的功能是:
def HasDupes(aseq) :
s = set()
return any(((x.lower() in s) or s.add(x.lower())) for x in aseq)
def GetDupes(aseq) :
s = set()
return set(x for x in aseq if ((x.lower() in s) or s.add(x.lower())))
熊猫用户应退房pandas.unique
。
>>> import pandas as pd
>>> lst = [1, 2, 1, 3, 3, 2, 4]
>>> pd.unique(lst)
array([1, 2, 3, 4])
该函数返回一个NumPy数组。如果需要,可以使用tolist
方法将其转换为列表。
seen.add
可能在两次迭代之间发生了变化,并且运行时不够智能,无法排除这种情况。为了安全起见,它必须每次检查对象。-如果您查看带有的字节码dis.dis(f)
,则可以看到它在每次迭代时都LOAD_ATTR
针对add
成员执行。ideone.com/tz1Tll