[解決済み] リスト一覧から重複を削除する
質問
Pythonでリストのリストを持っています。
k = [[1, 2], [4], [5, 6, 2], [1, 2], [3], [4]]
そして、そこから重複する要素を削除したいのです。リストでない普通のリストなら
set
. しかし、残念なことに、リストはハッシュ化できないので、リストの集合を作ることができません。タプルのみ。そこで、すべてのリストをタプルに変換して、setを使ってリストに戻すことができます。しかし、これは高速ではありません。
どうすれば一番効率よくできるのでしょうか?
上記のリストの結果は、次のようになるはずです。
k = [[5, 6, 2], [1, 2], [3], [4]]
保存順は気にしない。
注 この質問 は似ていますが、私が必要としているものとは全く違います。SOで検索してみましたが、完全な複製は見つかりませんでした。
ベンチマークを行う。
import itertools, time
class Timer(object):
def __init__(self, name=None):
self.name = name
def __enter__(self):
self.tstart = time.time()
def __exit__(self, type, value, traceback):
if self.name:
print '[%s]' % self.name,
print 'Elapsed: %s' % (time.time() - self.tstart)
k = [[1, 2], [4], [5, 6, 2], [1, 2], [3], [5, 2], [6], [8], [9]] * 5
N = 100000
print len(k)
with Timer('set'):
for i in xrange(N):
kt = [tuple(i) for i in k]
skt = set(kt)
kk = [list(i) for i in skt]
with Timer('sort'):
for i in xrange(N):
ks = sorted(k)
dedup = [ks[i] for i in xrange(len(ks)) if i == 0 or ks[i] != ks[i-1]]
with Timer('groupby'):
for i in xrange(N):
k = sorted(k)
dedup = list(k for k, _ in itertools.groupby(k))
with Timer('loop in'):
for i in xrange(N):
new_k = []
for elem in k:
if elem not in new_k:
new_k.append(elem)
短いリストでは "loop in" (quadratic method) が最も速いです。長いリストでは、groupbyメソッド以外のすべてのメソッドよりも高速です。これって意味があるんでしょうか?
短いリスト(コード内のもの)については、100000回の繰り返しです。
[set] Elapsed: 1.3900001049
[sort] Elapsed: 0.891000032425
[groupby] Elapsed: 0.780999898911
[loop in] Elapsed: 0.578000068665
より長いリスト(コード内のものが5回重複している)の場合。
[set] Elapsed: 3.68700003624
[sort] Elapsed: 3.43799996376
[groupby] Elapsed: 1.03099989891
[loop in] Elapsed: 1.85900020599
解決方法は?
>>> k = [[1, 2], [4], [5, 6, 2], [1, 2], [3], [4]]
>>> import itertools
>>> k.sort()
>>> list(k for k,_ in itertools.groupby(k))
[[1, 2], [3], [4], [5, 6, 2]]
itertools
この種の問題に対して、しばしば最も迅速かつ強力な解決策を提供し
さて
を使いこなす価値がありますね!)
編集 コメントにも書きましたが、通常の最適化作業は大きな入力に集中します(ビッグ・オー・アプローチ)。しかし、時には(基本的には、性能限界の限界に挑戦しているコードの深い内部ループの、悲劇的に重要なボトルネックについて)、確率分布を提供し、最適化する性能指標を決定し(アプリによっては、平均や中央値よりも、上限や90%セントが重要かもしれません)、入力データの特性に応じて異なるアルゴリズムを選ぶために開始時におそらく発見的チェックを実行するなど、より詳細に行う必要があるかもしれません。
ポイントパフォーマンス(特定の入力に対するコードA対コードB)の入念な測定は、この非常にコストのかかるプロセスの一部であり、標準ライブラリモジュール
timeit
ここで役立つのが しかし、シェルのプロンプトで使う方が簡単です。 例えば、この問題に対する一般的なアプローチを紹介するための短いモジュールがここにあります。
nodup.py
:
import itertools
k = [[1, 2], [4], [5, 6, 2], [1, 2], [3], [4]]
def doset(k, map=map, list=list, set=set, tuple=tuple):
return map(list, set(map(tuple, k)))
def dosort(k, sorted=sorted, xrange=xrange, len=len):
ks = sorted(k)
return [ks[i] for i in xrange(len(ks)) if i == 0 or ks[i] != ks[i-1]]
def dogroupby(k, sorted=sorted, groupby=itertools.groupby, list=list):
ks = sorted(k)
return [i for i, _ in itertools.groupby(ks)]
def donewk(k):
newk = []
for i in k:
if i not in newk:
newk.append(i)
return newk
# sanity check that all functions compute the same result and don't alter k
if __name__ == '__main__':
savek = list(k)
for f in doset, dosort, dogroupby, donewk:
resk = f(k)
assert k == savek
print '%10s %s' % (f.__name__, sorted(resk))
サニティチェックに注意してください(単に
python nodup.py
という基本的なテクニック(スピードアップのためにグローバルな定数名を各関数にローカルにする)を使って、対等な立場で物事を進めています。
これで、小さな例のリストに対してチェックを実行することができます。
$ python -mtimeit -s'import nodup' 'nodup.doset(nodup.k)'
100000 loops, best of 3: 11.7 usec per loop
$ python -mtimeit -s'import nodup' 'nodup.dosort(nodup.k)'
100000 loops, best of 3: 9.68 usec per loop
$ python -mtimeit -s'import nodup' 'nodup.dogroupby(nodup.k)'
100000 loops, best of 3: 8.74 usec per loop
$ python -mtimeit -s'import nodup' 'nodup.donewk(nodup.k)'
100000 loops, best of 3: 4.44 usec per loop
2 次アプローチは定数が小さいので、重複する値の少ない小さなリストでは魅力的であることが確認できました。 重複のない短いリストで
$ python -mtimeit -s'import nodup' 'nodup.donewk([[i] for i in range(12)])'
10000 loops, best of 3: 25.4 usec per loop
$ python -mtimeit -s'import nodup' 'nodup.dogroupby([[i] for i in range(12)])'
10000 loops, best of 3: 23.7 usec per loop
$ python -mtimeit -s'import nodup' 'nodup.doset([[i] for i in range(12)])'
10000 loops, best of 3: 31.3 usec per loop
$ python -mtimeit -s'import nodup' 'nodup.dosort([[i] for i in range(12)])'
10000 loops, best of 3: 25 usec per loop
二次的なアプローチも悪くないけど、sortやgroupbyの方が良いよ。 などなど。
もし(パフォーマンスへのこだわりが示唆するように)この操作が境界を押し広げるアプリケーションのコアな内部ループにあるならば、他の代表的な入力サンプルで同じテストのセットを試してみる価値があります。おそらく、ヒューリスティックにどちらかのアプローチを選ぶことができる簡単な尺度を検出することができるでしょう(ただし、尺度は当然ながら高速でなければなりません)。
を別の表現にしておくことも十分検討に値する。
k
-- そもそも、なぜタプルの集合ではなく、リストの集合でなければならないのでしょうか? もし、重複除去のタスクが頻繁に発生し、プロファイリングでそれがプログラムのパフォーマンスのボトルネックであることがわかれば、例えば、常にタプルのセットを保持し、必要なときだけそこからリストのリストを取得する方が全体的に速くなるかもしれません。
関連
-
Python入門 openを使ったファイルの読み書きの方法
-
[解決済み] リストのリストからフラットなリストを作るには?
-
[解決済み] リスト内のアイテムのインデックスを検索する
-
[解決済み] Pythonで2つのリストを連結する方法は?
-
[解決済み] リストが空かどうかを確認するにはどうすればよいですか?
-
[解決済み] Pythonのリストメソッドであるappendとextendの違いは何ですか?
-
[解決済み] リストを均等な大きさの塊に分割するには?
-
[解決済み] リストの最後の要素を取得する方法
-
[解決済み] リストからランダムに項目を選択するにはどうすればよいですか?
-
[解決済み] リスト内の重複を削除する
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
PythonはWordの読み書きの変更操作を実装している
-
Python百行で韓服サークルの画像クロールを実現する
-
[解決済み】RuntimeWarning: invalid value encountered in double_scalars で numpy の除算ができない。
-
[解決済み】TypeErrorの修正方法。Unicodeオブジェクトは、ハッシュ化する前にエンコードする必要がある?
-
[解決済み】なぜ「LinAlgError: Grangercausalitytestsから「Singular matrix」と表示されるのはなぜですか?
-
[解決済み] データ型が理解できない
-
[解決済み】Pythonスクリプトで「Expected 2D array, got 1D array instead: 」というエラーが発生?
-
[解決済み】Python Error: "ValueError: need more than 1 value to unpack" (バリューエラー:解凍に1つ以上の値が必要です
-
[解決済み】Python: SyntaxError: キーワードは式になり得ない
-
[解決済み] リストのリストで重複するリストを削除するには?[重複]する