How do you remove duplicates from a list whilst preserving order?
是否有一个内置的在保留顺序的同时从python的列表中删除重复项?我知道我可以使用集合来删除重复项,但这会破坏原始顺序。我也知道我可以像这样滚动:
1 2 3 4 5 6 | def uniq(input): output = [] for x in input: if x not in output: output.append(x) return output |
(感谢对该代码示例的释放。)
但如果可能的话,我想利用一个内建的或更为Python式的习语。
相关问题:在python中,从列表中删除重复项的最快算法是什么,以便所有元素在保持顺序的同时都是唯一的?
这里有一些备选方案:http://www.peterbe.com/plog/uniqifiers-benchmark
最快的一个:
1 2 3 4 | def f7(seq): seen = set() seen_add = seen.add return [x for x in seq if not (x in seen or seen_add(x))] |
为什么把
如果您计划在同一个数据集中大量使用此函数,那么最好使用一个有序的集合:http://code.activestate.com/recipes/528878/
o(1)每个操作的插入、删除和成员检查。
(小的附加说明:seen.add()总是返回none,因此上面的或上面的内容只是一种尝试集合更新的方法,而不是逻辑测试的一个组成部分。)
编辑2016
正如Raymond指出的那样,在用C实现
重要编辑2015
正如@abarnett所指出的,
1 2 3 4 | >>> from more_itertools import unique_everseen >>> items = [1, 2, 0, 1, 3, 2] >>> list(unique_everseen(items)) [1, 2, 0, 3] |
只需导入一个简单的库,无需进行黑客攻击。这来自ITertools配方
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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 接受的常见习惯用法(它起作用,但没有针对速度进行优化,我现在将使用
运行时间:O(n)
1 2 3 4 | >>> from collections import OrderedDict >>> items = [1, 2, 0, 1, 3, 2] >>> list(OrderedDict.fromkeys(items)) [1, 2, 0, 3] |
这看起来比:
1 2 | seen = set() [x for x in seq if x not in seen and not seen.add(x)] |
不使用丑陋的黑客:
1 | not seen.add(x) |
它依赖于这样一个事实:
但是请注意,尽管hack解决方案具有相同的运行时复杂性o(n),但它的原始速度更快。
在python 2.7中,从iterable中删除重复项的新方法是:
1 2 3 | >>> from collections import OrderedDict >>> list(OrderedDict.fromkeys('abracadabra')) ['a', 'b', 'r', 'c', 'd'] |
在Python3.5中,ordereddict有一个C实现。我的计时显示,现在这是Python3.5各种方法中最快和最短的。
在python 3.6中,常规dict既成了有序的又紧凑的。(此功能适用于cpython和pypy,但在其他实现中可能不存在)。这为我们提供了一种新的快速除尘方法,同时保持订单:
1 2 | >>> list(dict.fromkeys('abracadabra')) ['a', 'b', 'r', 'c', 'd'] |
在Python3.7中,常规dict保证在所有实现中都按顺序排列。因此,最短和最快的解决方案是:
1 2 | >>> list(dict.fromkeys('abracadabra')) ['a', 'b', 'r', 'c', 'd'] |
对@max的回应:一旦你移动到3.6或3.7并使用常规的dict而不是ordereddict,你就不能以任何其他方式真正击败性能。字典很密集,很容易转换成一个几乎没有开销的列表。目标列表被预先调整为len(d),它保存了列表理解中出现的所有大小。此外,由于内部键列表很密集,复制指针几乎和复制列表一样快。
1 2 3 | sequence = ['1', '2', '3', '3', '6', '4', '5', '6'] unique = [] [unique.append(item) for item in sequence if item not in unique] |
唯一→
1 2 | from itertools import groupby [ key for key,_ in groupby(sortedList)] |
列表甚至不需要排序,充分的条件是将相等的值分组在一起。
编辑:我假设"保留顺序"意味着列表实际上是有序的。如果不是这样,那么Mizardx的解决方案就是正确的。
社区编辑:然而,这是"将重复的连续元素压缩为单个元素"的最优雅的方法。
不要踢死马(这个问题很古老,已经有了很多好的答案),但这里有一个解决方案,使用熊猫在许多情况下都很快,而且非常容易使用。
1 2 3 4 5 6 7 | import pandas as pd my_list = [0, 1, 2, 3, 4, 1, 2, 3, 5] >>> pd.Series(my_list).drop_duplicates().tolist() # Output: # [0, 1, 2, 3, 4, 5] |
我想如果你想维持秩序,
您可以尝试以下操作:1 2 3 4 | list1 = ['b','c','d','b','c','a','a'] list2 = list(set(list1)) list2.sort(key=list1.index) print list2 |
或者类似地,您可以这样做:
1 2 3 | list1 = ['b','c','d','b','c','a','a'] list2 = sorted(set(list1),key=list1.index) print list2 |
您也可以这样做:
1 2 3 4 5 6 | list1 = ['b','c','d','b','c','a','a'] list2 = [] for i in list1: if not i in list2: list2.append(i)` print list2 |
也可以这样写:
1 2 3 4 | list1 = ['b','c','d','b','c','a','a'] list2 = [] [list2.append(i) for i in list1 if not i in list2] print list2 |
对于另一个很晚才回答的老问题:
- 处理标准的
key 功能。 - 不使用不体面的黑客。
- 通过预先绑定
seen.add 而不是查找n次来优化循环。(f7 也这样做,但有些版本没有。) - 通过使用
ifilterfalse 来优化循环,因此您只需循环python中的唯一元素,而不是所有元素。(当然,您仍然在ifilterfalse 中迭代所有这些函数,但这是在c中进行的,而且速度要快得多。)
它真的比
和所有的食谱一样,它也可以在
如果您只想使用no-
1 2 3 4 5 6 | def unique(iterable): seen = set() seen_add = seen.add for element in itertools.ifilterfalse(seen.__contains__, iterable): seen_add(element) yield element |
只需从外部模块1添加此类功能的另一个(非常有性能的)实现:
1 2 3 4 5 | >>> 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),这些显示它比我测试的所有其他替代方案都快,包括
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | %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() |
为了确保我也做了一个有更多重复的测试,只是为了检查它是否有影响:
1 2 3 4 5 6 | 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() |
一个只包含一个值:
1 2 3 4 | 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() |
在所有这些情况下,
此
1 2 3 4 | >>> lst = [{1}, {1}, {2}, {1}, {3}] >>> list(unique_everseen(lst)) [{1}, {2}, {3}] |
1免责声明:我是那个包裹的作者。
在python 3.7及更高版本中,字典保证记住它们的键插入顺序。这个问题的答案概括了目前的情况。
因此,
1 2 3 | >>> lst = [1, 2, 1, 3, 3, 2, 4] >>> list(dict.fromkeys(lst)) [1, 2, 3, 4] |
对于无哈希类型(例如列表列表),基于Mizardx:
1 2 3 | def f7_noHash(seq) seen = set() return [ x for x in seq if str( x ) not in seen and not seen.add( str( x ) )] |
5倍更快,减少变型,但更复杂
1 2 3 | >>> 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] |
说明:
1 2 3 4 5 6 7 8 9 10 11 12 | 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] |
借用haskell的
1 2 | def unique(lst): return [] if lst==[] else [lst[0]] + unique(filter(lambda x: x!= lst[0], lst[1:])) |
例如。:
1 2 | In [118]: unique([1,5,1,1,4,3,4]) Out[118]: [1, 5, 4, 3] |
我尝试了它来增加数据大小,并看到了次线性时间复杂性(不确定,但建议这对正常数据应该是好的)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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 |
我还认为有趣的是,这可以很容易地由其他操作推广到唯一性。这样地:
1 2 3 | 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) |
例如,您可以传入一个函数,该函数使用舍入到相同整数的概念,就像出于唯一性目的是"相等"一样,如下所示:
1 2 | def test_round(x,y): return round(x) != round(y) |
然后,unique(some_list,test_round)将提供列表中的唯一元素,其中unique不再意味着传统的相等性(这意味着使用任何基于集合或基于dict键的方法来解决此问题),而是意味着只取元素可能路由的每个整数k的第一个元素,该元素舍入为k。ND到,例如:
1 2 | 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] |
Mizardx的答案提供了多种方法的集合。
这就是我在大声思考时想到的:
1 | mylist = [x for i,x in enumerate(mylist) if x not in mylist[i+1:]] |
您可以引用一个列表理解,因为它是由符号"[1]"构建的。例如,下面的函数唯一地表示元素列表,而不通过引用其列表理解来更改它们的顺序。
1 2 | def unique(my_list): return [x for x in my_list if x not in locals()['_[1]']] |
演示:
1 2 3 | 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 | [1, 2, 3, 4, 5] |
你可以做一个丑陋的清单理解黑客。
1 | [l[i] for i in range(len(l)) if l.index(l[i]) == i] |
1 2 3 | l = [1,2,2,3,3,...] n = [] n.extend(ele for ele in l if ele not in set(n)) |
使用集合的O(1)查找来确定是否在新列表中包含元素的生成器表达式。
一个简单的递归解决方案:
1 2 | 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]] |
使用
1 2 | b = np.array([1,3,3, 8, 12, 12,12]) numpy.hstack([b[0], [x[0] for x in zip(b[1:], b[:-1]) if x[0]!=x[1]]]) |
输出:
1 | array([ 1, 3, 8, 12]) |
就地方法
这种方法是二次的,因为我们对列表中的每个元素都进行了线性查找(因为
也就是说,如果我们从列表的末尾开始,朝着原点移动,就可以在适当的位置操作,删除子列表左侧的每个术语。
代码中的这个想法很简单
1 2 | for i in range(len(l)-1,0,-1): if l[i] in l[:i]: del l[i] |
对实现的简单测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | 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]: |
如果您经常使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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 |
时机:
1 2 3 4 | 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 |
这将保持秩序并在O(n)时间内运行。基本上,我们的想法是在发现复制品的地方制造一个洞,并将其沉入底部。使用读写指针。每当发现重复项时,只有读指针前进,写指针停留在重复项上以覆盖它。
1 2 3 4 5 6 7 8 9 10 11 12 | 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] |
如果你需要一个内衬,那么这可能会有帮助:
1 | reduce(lambda x, y: x + y if y[0] not in x else x, map(lambda x: [x],lst)) |
…应该工作,但如果我错了就纠正我
不使用导入模块或集合的解决方案:
1 2 3 4 | 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) |
给出输出:
1 | ['ask', 'not', 'what', 'your', 'country', 'can', 'do', 'for', 'you'] |