Python でソートされた一意のリストを取得する最も高速な方法は何ですか? (ハッシュ可能なもののリストがあり、反復処理できるものが必要です。リストがインプレースで変更されるか、新しいリストを取得するか、反復可能なリストを取得するかは関係ありません。私の具体的な使用例では、使い捨てリストを使用してこれを実行しているため、インプレースの方がメモリ効率が高くなります。)
私は次のような解決策を見てきました
input = [5, 4, 2, 8, 4, 2, 1]
sorted(set(input))
しかし、最初に一意性をチェックしてからソートするのは無駄に思えます(リストをソートするときには、基本的に挿入ポイントを決定する必要があり、その結果、副作用として一意性テストが発生するためです)。おそらく、UNIXの
cat list | sort | uniq
すでにソートされたリスト内の連続した重複を選択するだけですか?
質問の「Pythonでリストを一意にする最も速い方法' リストはソートされておらず、 'Python リストで sort と uniq を実行する最もクリーンな方法は何ですか?' は最もクリーンで Python らしい方法を求めており、受け入れられた回答では が提案されていますがsorted(set(input))
、これを改善しようとしています。
ベストアンサー1
sorted(set(sequence))
これが最も速い方法だと思います。確かset
にシーケンスを反復しますが、これはCレベルのループであり、たくさんPython レベルで実行するループよりも高速です。
ただし、それでも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()
。