在python中获取排序的唯一列表的禁区方法是什么? (我有一个可清洗的东西列表,并希望有一些我可以迭代的东西 - 无论列表是否被修改到位,或者我得到一个新的列表,或者是一个可迭代的。在我的具体用例中,我'使用一次性列表执行此操作,因此就可以提高内存效率。)
我见过像
这样的解决方案input = [5, 4, 2, 8, 4, 2, 1]
sorted(set(input))
但在我看来,首先检查唯一性然后排序是浪费的(因为当您对列表进行排序时,您基本上必须确定插入点,因此将唯一性测试作为副作用)。也许还有更多类似于unix的内容
cat list | sort | uniq
只是在已排序的列表中选择连续重复?
在“Fastest way to uniqify a list in Python”问题中请注意,列表未排序,“What is the cleanest way to do a sort plus uniq on a Python list?”要求最干净/最pythonic方式,并且接受的答案建议sorted(set(input))
,我我试图改进。
答案 0 :(得分:24)
我相信sorted(set(sequence))
是最快的做法。
是的,set
遍历序列,但这是一个C级循环,很多比在python级别执行的任何循环更快。
请注意,即使使用groupby
,您仍然拥有O(n) + O(nlogn) = O(nlogn)
,而最糟糕的是groupby
将需要一个python级循环,这会大大增加O(n)
中的常量因此最终你会得到最差的结果。
在谈到CPython时,优化事物的方法是在C级别尽可能多地做(请参阅this回答,以获得反直觉性能的另一个例子)。要获得更快的解决方案,您必须在C扩展中重新实现排序。即便如此,祝你获得像python的Timsort一样快的东西!
“规范解决方案”与groupby
解决方案的小比较:
>>> import timeit
>>> sequence = list(range(500)) + list(range(700)) + list(range(1000))
>>> timeit.timeit('sorted(set(sequence))', 'from __main__ import sequence', number=1000)
0.11532402038574219
>>> import itertools
>>> def my_sort(seq):
... return list(k for k,_ in itertools.groupby(sorted(seq)))
...
>>> timeit.timeit('my_sort(sequence)', 'from __main__ import sequence, my_sort', number=1000)
0.3162040710449219
你可以看到慢3倍。
jdm提供的版本实际上更糟糕:
>>> def make_unique(lst):
... if len(lst) <= 1:
... return lst
... last = lst[-1]
... for i in range(len(lst) - 2, -1, -1):
... item = lst[i]
... if item == last:
... del lst[i]
... else:
... last = item
...
>>> def my_sort2(seq):
... make_unique(sorted(seq))
...
>>> timeit.timeit('my_sort2(sequence)', 'from __main__ import sequence, my_sort2', number=1000)
0.46814608573913574
慢了近5倍。
请注意,使用seq.sort()
然后make_unique(seq)
和make_unique(sorted(seq))
实际上是相同的,因为Timsort使用O(n)
空格,您总是会有一些重新分配,因此使用sorted(seq)
会实际上并没有改变很多时间。
jdm的基准测试给出了不同的结果,因为他使用的输入太小,因此所有时间都由time.clock()
次调用。
答案 1 :(得分:2)
也许这不是您正在寻找的答案,但无论如何,您应该考虑到这一点。
基本上,您在列表中有2个操作:
unique_list = set(your_list) # O(n) complexity
sorted_list = sorted(unique_list) # O(nlogn) complexity
现在,你说“在我看来,首先检查唯一性然后排序是浪费”,你是对的。但是,多余的步骤真的有多糟糕?取n = 1000000:
# sorted(set(a_list))
O(n) => 1000000
o(nlogn) => 1000000 * 20 = 20000000
Total => 21000000
# Your fastest way
O(nlogn) => 20000000
Total: 20000000
速度增益:(1 - 20000000/21000000)* 100 = 4.76%
对于n = 5000000,速度增益:~1.6%
现在,这种优化值得吗?
答案 2 :(得分:2)
这只是我在几分钟内掀起的事情。该函数修改了一个列表,并删除了连续的重复:
def make_unique(lst):
if len(lst) <= 1:
return lst
last = lst[-1]
for i in range(len(lst) - 2, -1, -1):
item = lst[i]
if item == last:
del lst[i]
else:
last = item
一些有代表性的输入数据:
inp = [
(u"Tomato", "de"), (u"Cherry", "en"), (u"Watermelon", None), (u"Apple", None),
(u"Cucumber", "de"), (u"Lettuce", "de"), (u"Tomato", None), (u"Banana", None),
(u"Squash", "en"), (u"Rubarb", "de"), (u"Lemon", None),
]
确保两种变体都按预期工作:
print inp
print sorted(set(inp))
# copy because we want to modify it in place
inp1 = inp[:]
inp1.sort()
make_unique(inp1)
print inp1
现在进行测试。我没有使用timeit,因为我不想复制列表,只需要排序。 time1
为sorted(set(...)
,time2
为list.sort()
,后跟make_unique
,而time3
为Avinash Y的itertools.groupby
解决方案。
import time
def time1(number):
total = 0
for i in range(number):
start = time.clock()
sorted(set(inp))
total += time.clock() - start
return total
def time2(number):
total = 0
for i in range(number):
inp1 = inp[:]
start = time.clock()
inp1.sort()
make_unique(inp1)
total += time.clock() - start
return total
import itertools
def time3(number):
total = 0
for i in range(number):
start = time.clock()
list(k for k,_ in itertools.groupby(sorted(inp)))
total += time.clock() - start
return total
sort + make_unique
大约与sorted(set(...))
一样快。我必须做几次迭代才能看到哪一个可能更快,但在变化中它们非常相似。 itertools
版本有点慢。
# done each 3 times
print time1(100000)
# 2.38, 3.01, 2.59
print time2(100000)
# 2.88, 2.37, 2.6
print time3(100000)
# 4.18, 4.44, 4.67
现在有一个更大的列表(+ str(i)
是为了防止重复):
old_inp = inp[:]
inp = []
for i in range(100):
for j in old_inp:
inp.append((j[0] + str(i), j[1]))
print time1(10000)
# 40.37
print time2(10000)
# 35.09
print time3(10000)
# 40.0
请注意,如果列表中有很多重复项,则第一个版本要快得多(因为排序较少)。
inp = []
for i in range(100):
for j in old_inp:
#inp.append((j[0] + str(i), j[1]))
inp.append((j[0], j[1]))
print time1(10000)
# 3.52
print time2(10000)
# 26.33
print time3(10000)
# 20.5
答案 3 :(得分:1)
>>> import itertools
>>> a=[2,3,4,1,2,7,8,3]
>>> list(k for k,_ in itertools.groupby(sorted(a)))
[1, 2, 3, 4, 7, 8]
答案 4 :(得分:1)
import numpy as np
np.unique(...)
np.unique函数返回ndarray唯一,并根据类似数组的参数进行排序。这适用于任何numpy类型,但也适用于可订购的常规python值。
如果您需要常规python列表,请使用np.unique(...).tolist()