web-dev-qa-db-ja.com

set()はどのように実装されていますか?

私は、pythonのsetオブジェクトがO(1)のメンバーシップをチェックしていると言う人々を見てきました。これを可能にするために、どのように内部的に実装されていますか?どのようなデータ構造を使用していますか?その実装には他にどのような影響がありますか?

ここでのすべての答えは本当に啓発的でしたが、私は1つしか受け入れることができないので、元の質問に最も近い答えで行きます。情報をありがとう。

135
Daenyth

このスレッド によると:

実際、CPythonのセットは、ダミー値(キーがセットのメンバーである)を持つ辞書のようなものとして実装され、この値の欠如を利用するいくつかの最適化があります。

したがって、基本的にsetは、基になるデータ構造としてハッシュテーブルを使用します。これはO(1)メンバーシップチェックを説明します。なぜなら、ハッシュテーブル内のアイテムの検索は平均でO(1)操作であるためです。

もしそうなら、 Achim Domma によれば、ほとんどはdict実装。

125
Justin Ethier

セットにO(1)メンバーシップチェックがあると言うとき、彼らはaverageの場合について話している。 worstケース(すべてのハッシュ値が衝突する場合)メンバーシップチェックはO(n)です。 時間の複雑さに関するPython wiki を参照してください。 。

Wikipediaの記事 は、サイズ変更しないハッシュテーブルのベストケース時間の複雑さはO(1 + k/n)であることを示しています。 Pythonセットはサイズ変更するハッシュテーブルを使用するため、この結果はPythonセットには直接適用されません。

ウィキペディアの記事でもう少し詳しく説明すると、averageの場合で、単純な均一ハッシュ関数を仮定すると、時間の複雑さはO(1/(1-k/n))ここで、k/nは定数c<1で区切ることができます。

Big-Oは、n→∞としての漸近的な動作のみを指します。 k/nは定数で区切ることができるため、c <1、nに依存せず、

O(1/(1-k/n))は、O(1/(1-c)) = O(constant)と同等のO(1)以下です。

したがって、averageで均一な単純なハッシュを仮定すると、Python setsのメンバーシップチェックはO(1)です。

73
unutbu

setルックアップ(またはそのことに関するハッシュテーブル)はよくある間違いだと思いますが、O(1)ではありません。
ウィキペディアから

最も単純なモデルでは、ハッシュ関数は完全に指定されておらず、テーブルのサイズは変更されません。ハッシュ関数の最適な選択のために、オープンアドレス指定のサイズnのテーブルには衝突がなく、n個までの要素を保持し、ルックアップを成功させるための単一の比較と、チェーンおよびkキーのサイズnのテーブルには最小max (0、kn)衝突とO(1 + k/n)ルックアップの比較。ハッシュ関数の最悪の選択では、すべての挿入が衝突を引き起こし、ハッシュテーブルは挿入ごとにΩ(k)の償却比較と成功したルックアップの最大k回の比較で線形検索に退化します。

関連: Java hashmap really O(1)?

13
Shay Erlichmen

全員が ソース に簡単にアクセスできます。set_lookkey()に先行するコメントは次のとおりです。

/* set object implementation
 Written and maintained by Raymond D. Hettinger <[email protected]>
 Derived from Lib/sets.py and Objects/dictobject.c.
 The basic lookup function used by all operations.
 This is based on Algorithm D from Knuth Vol. 3, Sec. 6.4.
 The initial probe index is computed as hash mod the table size.
 Subsequent probe indices are computed as explained in Objects/dictobject.c.
 To improve cache locality, each probe inspects a series of consecutive
 nearby entries before moving on to probes elsewhere in memory.  This leaves
 us with a hybrid of linear probing and open addressing.  The linear probing
 reduces the cost of hash collisions because consecutive memory accesses
 tend to be much cheaper than scattered probes.  After LINEAR_PROBES steps,
 we then use open addressing with the upper bits from the hash value.  This
 helps break-up long chains of collisions.
 All arithmetic on hash should ignore overflow.
 Unlike the dictionary implementation, the lookkey function can return
 NULL if the rich comparison returns an error.
*/


...
#ifndef LINEAR_PROBES
#define LINEAR_PROBES 9
#endif

/* This must be >= 1 */
#define PERTURB_SHIFT 5

static setentry *
set_lookkey(PySetObject *so, PyObject *key, Py_hash_t hash)  
{
...
13
gimel

set'sdict'sの違いをもう少し強調するために、ここではsetobject.cコメントセクションからの抜粋を示します。

セットの使用例は、検索されたキーが存在する可能性が高い辞書とはかなり異なります。対照的に、セットは主に、要素の存在が事前にわからないメンバーシップテストに関するものです。したがって、セットの実装では、見つかった場合と見つからない場合の両方について最適化する必要があります。

github のソース

2
user1767754