In Python, what is the fastest algorithm for removing duplicates from a list so that all elements are unique *while preserving order*?
例如:
1 2 3 | >>> x = [1, 1, 2, 'a', 'a', 3] >>> unique(x) [1, 2, 'a', 3] |
假设列表元素是可哈希的。
澄清:结果应保留清单中的第一份副本。例如,[1,2,3,2,3,1]变为[1,2,3]。
1 2 3 4 5 6 7 8 9 10 11 12 | def unique(items): found = set([]) keep = [] for item in items: if item not in found: found.add(item) keep.append(item) return keep print unique([1, 1, 2, 'a', 'a', 3]) |
使用:
1 | lst = [8, 8, 9, 9, 7, 15, 15, 2, 20, 13, 2, 24, 6, 11, 7, 12, 4, 10, 18, 13, 23, 11, 3, 11, 12, 10, 4, 5, 4, 22, 6, 3, 19, 14, 21, 11, 1, 5, 14, 8, 0, 1, 16, 5, 10, 13, 17, 1, 16, 17, 12, 6, 10, 0, 3, 9, 9, 3, 7, 7, 6, 6, 7, 5, 14, 18, 12, 19, 2, 8, 9, 0, 8, 4, 5] |
使用Timeit模块:
1 | $ python -m timeit -s 'import uniquetest' 'uniquetest.etchasketch(uniquetest.lst)' |
对于各种其他功能(我以它们的海报命名),我有以下结果(在我的第一代Intel MacBook Pro上):
1 2 3 4 5 6 7 8 9 | Allen: 14.6 μs per loop [1] Terhorst: 26.6 μs per loop Tarle: 44.7 μs per loop ctcherry: 44.8 μs per loop Etchasketch 1 (short): 64.6 μs per loop Schinckel: 65.0 μs per loop Etchasketch 2: 71.6 μs per loop Little: 89.4 μs per loop Tyler: 179.0 μs per loop |
[1]请注意,Allen在适当的位置修改了列表-我认为这已经扭曲了时间,因为
总结:用集合直接实现胜过混淆一行程序:—)
这里的解决方案fastest太远了(*以下输入:) P / < >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | def del_dups(seq): seen = {} pos = 0 for item in seq: if item not in seen: seen[item] = True seq[pos] = item pos += 1 del seq[pos:] lst = [8, 8, 9, 9, 7, 15, 15, 2, 20, 13, 2, 24, 6, 11, 7, 12, 4, 10, 18, 13, 23, 11, 3, 11, 12, 10, 4, 5, 4, 22, 6, 3, 19, 14, 21, 11, 1, 5, 14, 8, 0, 1, 16, 5, 10, 13, 17, 1, 16, 17, 12, 6, 10, 0, 3, 9, 9, 3, 7, 7, 6, 6, 7, 5, 14, 18, 12, 19, 2, 8, 9, 0, 8, 4, 5] del_dups(lst) print(lst) # -> [8, 9, 7, 15, 2, 20, 13, 24, 6, 11, 12, 4, 10, 18, 23, 3, 5, 22, 19, 14, # 21, 1, 0, 16, 17] |
词典lookup也更快slightly那么"设置"一个在Python 3。 P / < >
最快的速度取决于你的列表中有多少是重复的。如果几乎是所有重复项,只有很少的唯一项,那么创建新列表可能会更快。如果它主要是唯一的项目,从原始列表(或副本)中删除它们将更快。
下面是一个用于就地修改列表的列表:
1 2 3 4 5 6 7 8 | def unique(items): seen = set() for i in xrange(len(items)-1, -1, -1): it = items[i] if it in seen: del items[i] else: seen.add(it) |
向后迭代索引可以确保移除项不会影响迭代。
这是我找到的最快的就地方法(假设有大量重复):
1 2 3 4 5 | def unique(l): s = set(); n = 0 for x in l: if x not in s: s.add(x); l[n] = x; n += 1 del l[n:] |
这比Allen的实现快10%,它是基于这个实现的(用timeit.repeat计时,由psyco编译的jit)。它保留任何副本的第一个实例。
雷普顿:如果你能确认我的时间,我会很感兴趣的。
这是可能的simplest方式: P / < >
1 | list(OrderedDict.fromkeys(iterable)) |
20世纪的Python 3.5,ordereddict现在implemented在C,所以这会是现在的shortest,cleanest,和fastest。 P / < >
基于发电机的强制性变化:
1 2 3 4 5 6 | def unique(seq): seen = set() for x in seq: if x not in seen: seen.add(x) yield x |
一班轮:
1 | new_list = reduce(lambda x,y: x+[y][:1-int(y in x)], my_list, []) |
这就是一个fastest,comparing所有的东西从这lengthy discussion和其他的回答给了睾丸,refering给该基准。它的另一个25 %的速度比的fastest功能从discussion,
1 2 3 4 | def uniquify(seq): seen = set() seen_add = seen.add return [x for x in seq if x not in seen and not seen_add(x)] |
一定的时间比较: P / < >
1 2 3 4 5 6 | $ python uniqifiers_benchmark.py * f8_original 3.76 * uniquify 3.0 * terhorst 5.44 * terhorst_localref 4.08 * del_dups 4.76 |
在一个地方的一个liner为这样: P / < >
1 2 3 | >>> x = [1, 1, 2, 'a', 'a', 3] >>> [ item for pos,item in enumerate(x) if x.index(item)==pos ] [1, 2, 'a', 3] |
摘自http://www.peterbe.com/plog/uniqifiers-benchmark
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | def f5(seq, idfun=None): # order preserving if idfun is None: def idfun(x): return x seen = {} result = [] for item in seq: marker = idfun(item) # in old Python versions: # if seen.has_key(marker) # but in new ones: if marker in seen: continue seen[marker] = 1 result.append(item) return result |
实际上,您可以在Python中做一些非常酷的事情来解决这个问题。您可以创建一个列表理解,在构建时引用它自己。如下:
1 2 3 | # remove duplicates... def unique(my_list): return [x for x in my_list if x not in locals()['_[1]'].__self__] |
编辑:我删除了"self",它在mac os x,python 2.5.1上工作。
_[1]是python对新列表的"秘密"引用。当然,上面有点混乱,但是你可以根据需要调整它。例如,您实际上可以编写一个返回对理解的引用的函数;它看起来更像:
1 | return [x for x in my_list if x not in this_list()] |
remove duplicates和维护秩序。 P / < >
这是一个快速的2 liner,leverages建在functionality of comprehensions列表和dicts。 P / < >
1 2 3 4 5 6 7 | x = [1, 1, 2, 'a', 'a', 3] tmpUniq = {} # temp variable used below results = [tmpUniq.setdefault(i,i) for i in x if i not in tmpUniq] print results [1, 2, 'a', 3] |
"dict.setdefaults(功能)的价值returns号好号添加到高温dict直接在comprehension列表。用"建在功能和hashes之dict将工作要maximize效率为过程。 P / < >
是否必须首先将重复项放在列表中?在向上查找元素时没有开销,但是在添加元素时会有一些开销(尽管开销应该是O(1))。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | >>> x = [] >>> y = set() >>> def add_to_x(val): ... if val not in y: ... x.append(val) ... y.add(val) ... print x ... print y ... >>> add_to_x(1) [1] set([1]) >>> add_to_x(1) [1] set([1]) >>> add_to_x(1) [1] set([1]) >>> |
在python中has_key是o(1)。哈希的插入和检索也是O(1)。两次循环N个项目,所以o(n)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | def unique(list): s = {} output = [] for x in list: count = 1 if(s.has_key(x)): count = s[x] + 1 s[x] = count for x in list: count = s[x] if(count > 0): s[x] = 0 output.append(x) return output |
o(n)如果dict是hash,o(nlogn)如果dict是tree,并且简单、固定。感谢马修的建议。抱歉,我不知道底层类型。
1 2 3 4 5 6 7 8 9 10 11 | def unique(x): output = [] y = {} for item in x: y[item] ="" for item in x: if item in y: output.append(item) return output |
这里有一些伟大的,有效的解决方案。但是,对于不关心绝对最有效的
1 2 | def unique(xs): return sorted(set(xs), key=lambda x: xs.index(x)) |
或更有效的双内衬
1 2 3 | def unique(xs): positions = dict((e,pos) for pos,e in reversed(list(enumerate(xs)))) return sorted(set(xs), key=lambda x: positions[x]) |
这里是两个recipes从itertools文件: P / < >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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 ifilterfalse(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 def unique_justseen(iterable, key=None): "List unique elements, preserving order. Remember only the element just seen." # unique_justseen('AAAABBBCCDAABBB') --> A B C D A B # unique_justseen('ABBCcAD', str.lower) --> A B C A D return imap(next, imap(itemgetter(1), groupby(iterable, key))) |
我对python没有经验,但是一种算法是对列表进行排序,然后删除重复项(通过与列表中以前的项进行比较),最后通过与旧列表进行比较在新列表中找到位置。
更长的答案:http://aspn.activestate.com/aspn/cookbook/python/recipe/52560
1 2 3 4 5 6 | >>> def unique(list): ... y = [] ... for x in list: ... if x not in y: ... y.append(x) ... return y |
如果从terhost的答案中的call to set()中去掉空列表,您会得到一点速度提升。
变化:发现=集([])到:SED= SET()
不过,你根本不需要这套。
1 2 3 4 5 6 7 8 | def unique(items): keep = [] for item in items: if item not in keep: keep.append(item) return keep |
使用Timeit,我得到了以下结果:
带套件([])--4.97210427363带set()--4.65712377445不带套件--3.44865284975
1 2 3 4 5 6 7 8 9 10 | x = [] # Your list of items that includes Duplicates # Assuming that your list contains items of only immutable data types dict_x = {} dict_x = {item : item for i, item in enumerate(x) if item not in dict_x.keys()} # Average t.c. = O(n)* O(1) ; furthermore the dict comphrehension and generator like behaviour of enumerate adds a certain efficiency and pythonic feel to it. x = dict_x.keys() # if you want your output in list format |
1 2 3 4 | >>> x=[1,1,2,'a','a',3] >>> y = [ _x for _x in x if not _x in locals()['_[1]'] ] >>> y [1, 2, 'a', 3] |
"locals()["[1]"]是正在创建的列表的"秘密名称"。
我不知道这是不是很快,但至少很简单。
简单地说,先把它转换成一个集合,然后再转换成一个列表
1 2 | def unique(container): return list(set(container)) |
在1,2,3,4,5,7,7,8,8,9,9,3,45 = [ ] P / < >
独特的DEF(L): P / < >
1 2 3 4 5 | ids={} for item in l: if not ids.has_key(item): ids[item]=item return ids.keys() |
打印的 P / < >
打印独特(的) P / < > ----------------------------
inserting元素将以θ(n) 如果retrieving元也exiting或不会把时间常数 所有的测试项目也将把θ(n) 所以我们可以看到,这种解决方案将把θ(n) 熊在所有的词典,在Python implemented by哈希表 P / < >
我没有做任何测试,但是一个可能的算法是创建第二个列表,并遍历第一个列表。如果项目不在第二个列表中,请将其添加到第二个列表中。
1 2 3 4 5 | x = [1, 1, 2, 'a', 'a', 3] y = [] for each in x: if each not in y: y.append(each) |
一次传球。
1 2 3 4 5 6 7 8 9 10 11 12 13 | a = [1,1,'a','b','c','c'] new_list = [] prev = None while 1: try: i = a.pop(0) if i != prev: new_list.append(i) prev = i except IndexError: break |