簡體   English   中英

在python中獲取排序唯一列表的最快方法?

[英]Fastest way to get sorted unique list in python?

在python中獲取排序,唯一列表的禁區方法是什么? (我有一個可清洗的東西列表,並希望有一些我可以迭代的東西 - 無論列表是否被修改到位,或者我得到一個新的列表,或者是一個可迭代的。在我的具體用例中,我'使用一次性列表執行此操作,因此就可以提高內存效率。)

我見過類似的解決方案

input = [5, 4, 2, 8, 4, 2, 1]
sorted(set(input))

但在我看來,首先檢查唯一性然后排序是浪費的(因為當您對列表進行排序時,您基本上必須確定插入點,因此將唯一性測試作為副作用)。 也許還有更多類似於unix的東西

cat list | sort | uniq

只是在已經排序的列表中選擇連續重復?


請注意“ 在Python中使用最快的方式來統一列表 ”這個問題,列表沒有排序,並且' 在Python列表中進行排序加uniq的最 簡潔方法什么? '要求最干凈/最pythonic的方式,接受的答案建議sorted(set(input)) ,我正在努力改進。

我相信sorted(set(sequence))是最快的方法。 是的, set遍歷序列,但是這是一個C級循環,這是比任何循環,你將在蟒蛇的水平更快的速度做了很多

請注意,即使使用groupby你仍然有O(n) + O(nlogn) = O(nlogn)而且最糟糕的是groupby將需要一個python級循環,這會大大增加O(n)中的常量,從而在結束你獲得最差的結果。

在談到CPython時,優化事物的方法就是在C級別盡可能多地做到這一點 (請參閱答案以獲得反直覺性能的另一個例子)。 要獲得更快的解決方案,您必須在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()調用。

也許這不是你要找的答案,但無論如何,你應該考慮到這一點。

基本上,您在列表上有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%

現在,這種優化值得嗎?

這只是我在幾分鍾內掀起的事情。 該函數修改了一個列表,並刪除了連續的重復:

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,因為我不想復制列表,只需要排序。 time1sorted(set(...)time2list.sort()后跟make_uniquetime3time3 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大約和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
>>> 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]
import numpy as np
np.unique(...)

np.unique函數返回唯一的ndarray,並根據類似數組的參數進行排序。 這適用於任何numpy類型,但也適用於可訂購的常規python值。

如果你需要一個普通的python列表,請使用np.unique(...).tolist()

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM