Python:ルックアップテーブルのリストと辞書


169

あるタイプのルックアップテーブルに配置する必要がある値が約1,000万個あるので、リストまたは辞書のどちらがより効率的か疑問に思っていましたか?

私はあなたが両方のためにこのようなことをすることができることを知っています:

if something in dict_of_stuff:
    pass

そして

if something in list_of_stuff:
    pass

私の考えは、口述がより速く、より効率的になるだろうということです。

ご協力いただきありがとうございます。

編集1
私がやろうとしていることに関するもう少し詳しい情報。 オイラー問題92。計算された値がすべて計算済みかどうかを確認するために、ルックアップテーブルを作成しています。

編集2
ルックアップの効率。

編集3
値に関連する値はありません...それで、セットはより良いでしょうか?


1
何の面で効率性?インサート?調べる?メモリ消費?価値の純粋な存在を確認していますか、それともメタデータが関連付けられていますか?
truppo 2009

余談ですが、その特定の問題には1000万のリストやディクテーションは必要ありませんが、はるかに小さいものです。
sfotiadis 2014

回答:


222

速度

リストのルックアップはO(n)、ディクショナリのルックアップはデータ構造内のアイテム数に関して、償却されたO(1)です。値を関連付ける必要がない場合は、セットを使用します。

記憶

辞書とセットはどちらもハッシュを使用し、オブジェクトのストレージだけに使用するよりもはるかに多くのメモリを使用します。AMAM Kuchling in Beautiful Codeによると、実装はハッシュを2/3いっぱいに維持しようとするため、かなりのメモリを浪費する可能性があります。

その場で新しいエントリを追加しない場合(更新した質問に基づいて追加します)、リストを並べ替えてバイナリ検索を使用することは価値があります。これはO(log n)であり、文字列では遅くなる可能性が高く、自然な順序付けのないオブジェクトでは不可能です。


6
はい。ただし、内容が変更されない場合は、1回限りの操作です。二分探索はO(log n)です。
Torsten Marek、

1
@John Fouhy:intはハッシュテーブルに格納されず、ポインターのみが格納されます。つまり、houにはintが40M(まあ、実際にはそれらの多くが小さい場合はありません)とハッシュテーブルには60Mがあります。今日はそれほど問題ではないことに同意しますが、覚えておく価値はあります。
Torsten Marek、

2
これは古い質問ですが、償却されたO(1)は、非常に大きなセット/ディクテーションには当てはまらない可能性があると思います。wiki.python.org/moin/TimeComplexityによる最悪のシナリオはO(n)です。それは、平均時間がO(1)から分岐し、O(n)に収束し始める時点の内部ハッシュ実装に依存していると思います。最適なセットサイズを取得する必要がある限り、グローバルセットをいくつかの容易に識別可能な属性(最初の桁の値、次に2番目、3番目など)に基づいて小さなセクションに区分することにより、検索パフォーマンスを向上できます。 。
Nisan.H 2012

3
@TorstenMarekこれは私を混乱させます。このページから、リスト検索はO(1)で、辞書検索はO(n)です。これは、あなたが言ったことの逆です。私は誤解していますか?
temporary_user_name

3
@Aerovistaeそのページの情報を間違って読んだと思います。リストの下に、 "x in s"(ルックアップ)のO(n)があります。また、O(1)の平均的なケースとしてのセットとディクトのルックアップも示しています。
デニス

45

dictはハッシュテーブルなので、キーを見つけるのは本当に速いです。したがって、dictとlistの間では、dictの方が高速です。ただし、関連付ける値がない場合は、セットを使用することをお勧めします。「テーブル」の部分がないハッシュテーブルです。


編集:あなたの新しい質問のために、はい、セットが良いでしょう。2つのセットを作成するだけで、1つが1で終わるシーケンス用に1つ、もう1つが89で終わるシーケンス用に1つ作成します。セットを使用してこの問題をうまく解決しました。


35

set()まさにあなたが望むものです。O(1)ルックアップ。dictよりも小さい。


31

私はいくつかのベンチマークを行いましたが、dictはリストと大規模なデータセットの両方よりも高速で、Linuxのi7 CPUでpython 2.7.3を実行しています。

  • python -mtimeit -s 'd=range(10**7)' '5*10**6 in d'

    10ループ、最高3:ループあたり64.2ミリ秒

  • python -mtimeit -s 'd=dict.fromkeys(range(10**7))' '5*10**6 in d'

    10000000ループ、ベスト3:ループあたり0.0759 usec

  • python -mtimeit -s 'from sets import Set; d=Set(range(10**7))' '5*10**6 in d'

    1000000ループ、ベスト3:ループあたり0.262 usec

ご覧のとおり、dictはlistよりもかなり高速で、setよりも約3倍高速です。ただし、一部のアプリケーションでは、その美しさを考慮してセットを選択することもできます。そして、データセットが本当に小さい場合(<1000要素)、リストはかなりうまく機能します。


正反対ではないでしょうか?リスト:10 * 64.2 * 1000 = 642000 usec、dict:10000000 * 0.0759 = 759000 usecおよびセット:1000000 * 0.262 = 262000 usec ...したがって、セットが最も速く、リストが続き、例では最後にdictが続きます。それとも何か不足していますか?
andzep

1
...しかし、ここでの質問は、この時間は実際には何を測定しているのですか?特定のリスト、dict、セットのアクセス時間ではなく、さらに、リスト、dict、セットを作成し、最後に1つの値を検索してアクセスするための時間とループ。それで、これはまったく質問と関係がありますか?...それは興味深いけど...
andzep

8
@andzep、あなたは間違ってい-sます。オプションはtimeit環境をセットアップすることです。つまり、合計時間には含まれません。-sオプションは1回だけ実行されます。Python 3.3では、次の結果が得られます。gen(範囲)-> 0.229 usec、list-> 157ミリ秒、dict-> 0.0806 usec、set-> 0.0807 usec。セットとディクテーションのパフォーマンスは同じです。ただし、Dictは初期化に設定よりも少し時間がかかります(合計時間13.580秒対11.803秒)
sleblanc

1
組み込みのセットを使用しないのはなぜですか?私は実際に組み込みセット()よりもsets.Set()とはるかに悪い結果を得る
トーマス・ギヨー-Sionnest

2
@ ThomasGuyot-Sionnest組み込みセットはpython 2.4で導入されたため、提案されたソリューションでなぜそれを使用しなかったのかわかりません。python -mtimeit -s "d=set(range(10**7))" "5*10**6 in d"Python 3.6.0(10000000ループ、ベスト3:ループあたり0.0608 usec)を使用すると良好なパフォーマンスが得られます。これは、dictベンチマークとほぼ同じなので、コメントをありがとうございます。
EriF89 2017年

6

あなたは口述が欲しい。

Pythonの(ソートされていない)リストの場合、「in」操作にはO(n)時間を必要とします---大量のデータがある場合は適切ではありません。一方、dictはハッシュテーブルであるため、O(1)ルックアップ時間を期待できます。

他の人が指摘したように、キーと値のペアではなくキーしかない場合は、代わりにセット(特別なタイプのdict)を選択できます。

関連:

  • Python wiki:Pythonコンテナー操作の時間の複雑さに関する情報。
  • SO:Pythonコンテナーの操作時間とメモリの複雑さ

1
ソートされたリストの場合でも、「in」はO(n)です。

2
リンクされたリストについては、はい---しかし、Pythonの「リスト」は、ほとんどの人がベクトルと呼ぶものであり、ソートされたときにO(1)でインデックス付きアクセスを提供し、O(log n)で検索操作を提供します。
zweiterlinde 2009

inソートされたリストに適用された演算子は、(ランダムな値の検索のために)ソートされていないリストに適用された場合よりもパフォーマンスが良いと言っていますか?(それらがベクターとして、またはリンクされたリストのノードとして内部的に実装されているかどうかは関係ないと思います。)
martineau 2010年

4

データが一意である場合set()が最も効率的ですが、2つあります-dict(一意性も必要です、おっと:)


私は自分の答えが投稿された%)を見たときに気づきました
SilentGhost

2
@SilentGhost答えが間違っている場合、なぜそれを削除しないのですか?あまりにもupvotesに悪いが、それは(まあ、たまたま起こった
ジャン・フランソワ・ファーブル

3

@ EriF89がこれらの年のすべての後にまだ正しいことを示す新しいテストセットとして:

$ python -m timeit -s "l={k:k for k in xrange(5000)}"    "[i for i in xrange(10000) if i in l]"
1000 loops, best of 3: 1.84 msec per loop
$ python -m timeit -s "l=[k for k in xrange(5000)]"    "[i for i in xrange(10000) if i in l]"
10 loops, best of 3: 573 msec per loop
$ python -m timeit -s "l=tuple([k for k in xrange(5000)])"    "[i for i in xrange(10000) if i in l]"
10 loops, best of 3: 587 msec per loop
$ python -m timeit -s "l=set([k for k in xrange(5000)])"    "[i for i in xrange(10000) if i in l]"
1000 loops, best of 3: 1.88 msec per loop

ここでは、いくつかの使用例tupleよりも高速であるlists(およびメモリ使用量が少ない)ことがわかっているも比較します。ルックアップテーブルの場合、tupleフェアフェアは良くありません。

両方dictset非常によく行きました。これは、一意性に関する@SilentGhost回答に結びついた興味深いポイントをもたらします。OPがデータセットに10Mの値を持ち、それらに重複があるかどうかが不明な場合、その要素のセット/ディクテーションを並行して保持する価値があります。実際のデータセットと、そのセット/辞書に存在するかどうかのテスト。10Mのデータポイントに一意の値が10個しかない可能性があります。これは、検索するスペースがはるかに小さいことを意味します。

dictを使用して重複したデータ(値)を重複していないセット(キー)に関連付け、1つのデータオブジェクトを保持してすべてのデータを保持しながら、ルックアップテーブルとして高速であるため、dictsに関するSilentGhostの間違いは実際に明らかです。たとえば、dictキーは検索される値であり、値はその値が発生した架空のリスト内のインデックスのリストである可能性があります。

たとえば、検索するソースデータリストがの場合、l=[1,2,3,1,2,1,4]次の辞書に置き換えることで、検索とメモリの両方に対して最適化できます。

>>> from collections import defaultdict
>>> d = defaultdict(list)
>>> l=[1,2,3,1,2,1,4]
>>> for i, e in enumerate(l):
...     d[e].append(i)
>>> d
defaultdict(<class 'list'>, {1: [0, 3, 5], 2: [1, 4], 3: [2], 4: [6]})

この口述で、人は知ることができます:

  1. 値が元のデータセットにあった場合(つまり、を2 in d返しますTrue
  2. ここで、値は、元のデータセット内であった(すなわち、d[2]データが元のデータリストで発見されたインデックスのリストが返されます。[1, 4]

最後の段落については、それを読むのは理にかなっていますが、説明しようとしている実際のコードを見るとよいでしょう(おそらく理解しやすいでしょう)。
カイザー

0

実際にテーブルに1000万個の値を格納する必要はないので、どちらの方法でも大したことはありません。

ヒント:最初の二乗和演算後の結果の大きさを考えてください。可能な最大の結果は1000万よりはるかに小さくなります...

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.