Python での Dict 実装の原理は何ですか?
1. 順序なし Dict の実装
Dict は、空間対時間戦略とハッシュ テーブル実装のおかげで、キーをすばやく見つけることができます。キーの読み取りおよび書き込み時には、キーがハッシュ化され (そのため、キーは不変型である必要があります。変数型の場合、ハッシュ値は計算できません)、計算された値と現在の配列空間の長さは剰余的に計算され、取得された値は配列内の現在の Key の添字です。最後に、添字を通じて、O(1) の時間計算量で値を読み取ることができます。この実装は優れています。は分散への一般的なアプローチですが、問題もあります。配列がいっぱいであるかキーが異なる場合、ハッシュ結果が同じ場合はどうすればよいですか?
最初の問題の解決策は、容量を拡張する際、Python
では、Dict に配置された数値が容量の 2/3 を占めると、Dict が拡張を開始し、拡張後の総容量は拡張前の 2 倍になります。これは、頻繁な拡張によるキーの移行の増加を減らすためです。
2 番目の問題には 2 つの解決策があります:
リンク メソッド: 元の配列には、次の値に対応する値が含まれます。 Key、連鎖メソッドの配列には配列が格納されます。この配列には、以下に示すように、キーと対応する値を含む配列が格納されます。key1 と key2 のハッシュ結果が両方とも 0 であると仮定して、In に挿入されます。配列の 0 の添字、key1 は 0 の添字配列の最初の位置にあり、key2 が挿入されると、key1 がすでに存在することがわかります。次に、key2 と key1 を比較して、それらのキーが実際に異なることを確認し、次に追加します。 0 subscript.
array = [ [ # 分别为key, hash值, 数值 ('key1', 123, 123), ('key2', 123, 123) ], [ ('key3', 123, 123) ] ]
アドレッシング方法の開発: アドレッシング方法の開発では、データを挿入するときに競合が発生した場合に、借用のアイデアを採用する別のアイデアが採用されています。現在の添字の次の桁です。次の桁がまだ競合する場合は、次の桁を使用し続けます。データを検索するとき、ハッシュ値に対応するキーが比較されます。値があり、一致しない場合は、次のビット、空席が見つかるまで、または空席が見つかるまで。
上記の 2 つのソリューションの実装は非常に簡単で、比較することでそれぞれの長所と短所を簡単に知ることができます:
リンク リスト方式の利点:
- #レコードの削除に便利です。配列の添字に対応する部分配列を直接処理できます。
- 平均検索速度が高速です。競合がある場合は、 、サブ配列をクエリするだけで済みます
- ポインタを使用するため、クエリ速度が遅くなり、速度が速くなります。メモリ使用量が多く、シリアル化には適していません。オープン アドレッシング方式の長所と短所は、リンク リスト方式とは逆です。Python ではすべてが Dict に基づいており、シリアル化する必要があるため、オープン アドレッシング方式が選択されました。
Python の Dict の値は 8 です。さらに、ハッシュ テーブルにはデータの配列を保存できる必要もあります。未使用の空席は範囲内で変動するため、スペースの使用量とハッシュ衝突の確率が高くなります。容量は最適な状態に維持されますが、拡張するたびに多大なパフォーマンスを消費するため、毎回変更することはできません。拡張するため、値を決定する必要があります。未使用/使用率がこの値に達すると、容量は自動的に変更されます
Python の Dict では、この値は 2/3 です。つまり、Dict が使用されると、スペースの 2/3 が占有された後、新しい最適なバランスに達するために自動的に拡張されます。同時に、拡張ごとのキーの移行数を減らすために、拡張後の総容量は拡張前の総容量の 3 分の 1 倍にする必要があり、この場合、キーの数は半分だけで済みます。
arrray = [None, None, None, None, True, True, True, True]
以True代表当前数组的位置已经被占用, None代表未被占用, 可以看出当前并未满足使用了数组的2/3空间, 数组还未到扩容阶段。 此时假设要插入一个Key, 刚好落在数组下标4, 那么插入的时候就要一直查询下去, 最后发现只有数组下标0的位置的空的, 才可以真正的插入数据. 对于一个长度只有8的数组, 需要执行5次才能插入数据, 那也太浪费性能了, 所以Python
要实现一个算法, 尽量让冲突的数据插在别的地方. 在源码中, Python
用到了公式x = ((5*y) + 1) % 2**i
来实现跳跃插入冲突数据. 式子中x为数组的下一个坐标, y为当前发生冲突的坐标,i为容量系数, 比如初始化时, i为3, 那么容量就是8了, 第一次插入数据到坐标0冲突时, y = 0, 带入公式后, 求得x 等于1, 第二次插入数据到坐标0时, y = 1, 求得x 等于 6, 通过这样算下去, 可以发现数字生成轨迹是0>1>6>7>4>5>2>3>0一直循环着, 这样跳着插数据就能完美解决上面场景的问题.
2.有序Dict的原理
Python
3.6之前说的差不多, 它的数组大概是长这样的, 数组中存了子数组, 第一项为hash值, 第二项为key, 第三项为值.
array = [ [], [123456, "key1", 1], [], [], [], [234567, "key2", 2], [], [] ]
这种实现的空间大小在初始化时就固定了, 直到下次扩容再发送变化, 在遍历字典时, 实际上就是遍历数组, 只是把没有占用的空间进行跳过.可以看出这种遍历的生成的顺序只跟哈希结果相关, 无法跟插入顺序相关, 所以这种方法的实现是无序的(同时由于每次启动程序时, 他们的哈希计算是不一样的, 所以每次遍历的顺序也就各不相同了).
而在Python
3.7之后, Python
的Dict使用了新的数据结构, 使Python
新Dict的内存占用也比老的Dict少了, 同时新的Dict在遍历时是跟插入顺序是一致的, 具体的实现是, 初始化时会生成两个数组, 插入值时, 在数组二追加当前的数据, 并获得当前追加数据所在的下标A, 然后对key进行哈希取模算出下标B, 最后对数组下标B的值更新为A, 简单的演示如下:
# 初始的结构 # -1代表还未插入数据 array_1 = [-1, -1, -1, -1, -1, -1, -1, -1] array_2 = [] # 插入值后, 他就会变为: array_1 = [-1, 0, -1, -1, -1, 1, -1, -1] array_2 = [ [123456, "key1", 1], [234567, "key2", 2], ]
可以看出, 数组2的容量跟当前放入的值相等的, 数组1虽然还会保持1/3的空闲标记位, 但他只保存数组二的下标, 占用空间也不多, 相比之前的方案会节省一些空间, 同时在遍历的时候可以直接遍历数组2, 这样Python
的Dict就变为有序的了. 注: 为了保持操作高效, 在删除的时候, 只是把数组1的值设置为-2, 并把数组2对应的值设置为None, 而不去删除它, 在查找时会忽略掉数组1中值为-2的元素, 在遍历时会忽略掉值为None的元素.
3.有序字典的实现
通过上面的了解后, 可以使用Python
来写一个新版Dict的实现, 具体说明见注释:
from typing import Any, Iterable, List, Optional, Tuple class CustomerDict(object): def __init__(self): self._init_seed: int = 3 # 容量因子 self._init_length: int = 2 ** self._init_seed # 初始化数组大小 self._load_factor: float = 2 / 3 # 扩容因子 self._index_array: List[int] = [-1 for _ in range(self._init_length)] # 存放下标的数组 self._data_array: List[Optional[Tuple[int, Any, Any]]] = [] # 存放数据的数组 self._used_count: int = 0 # 目前用的量 self._delete_count: int = 0 # 被标记删除的量 def _create_new(self): """扩容函数""" self._init_seed += 1 # 增加容量因子 self._init_length = 2 ** self._init_seed old_data_array: List[Tuple[int, Any, Any]] = self._data_array self._index_array: List[int] = [-1 for _ in range(self._init_length)] self._data_array: List[Tuple[int, Any, Any]] = [] self._used_count = 0 self._delete_count = 0 # 这里只是简单实现, 实际上只需要搬运一半的数据 for item in old_data_array: if item is not None: self.__setitem__(item[1], item[2]) def _get_next(self, index: int): """计算冲突的下一跳,如果下标对应的值冲突了, 需要计算下一跳的下标""" return ((5*index) + 1) % self._init_length def _core(self, key: Any, default_value: Optional[Any] = None) -> Tuple[int, Any, int]: """获取数据或者得到可以放新数据的方法, 返回值是index_array的索引, 数据, data_array的索引""" index: int = hash(key) % (self._init_length - 1) while True: data_index: int = self._index_array[index] # 如果是-1则代表没有数据 if data_index == -1: break # 如果是-2则代表之前有数据则不过被删除了 elif data_index == -2: index = self._get_next(index) continue _, new_key, default_value = self._data_array[data_index] # 判断是不是对应的key if key != new_key: index = self._get_next(index) else: break return index, default_value, data_index def __getitem__(self, key: Any) -> Any: _, value, data_index = self._core(key) if data_index == -1: raise KeyError(key) return value def __setitem__(self, key: Any, value: Any) -> None: if (self._used_count / self._init_length) > self._load_factor: self._create_new() index, _, _ = self._core(key) self._index_array[index] = self._used_count self._data_array.append((hash(key), key, value)) self._used_count += 1 def __delitem__(self, key: Any) -> None: index, _, data_index = self._core(key) if data_index == -1: raise KeyError(key) self._index_array[index] = -2 self._data_array[data_index] = None self._delete_count += 1 def __len__(self) -> int: return self._used_count - self._delete_count def __iter__(self) -> Iterable: return iter(self._data_array) def __str__(self) -> str: return str({item[1]: item[2] for item in self._data_array if item is not None}) def keys(self) -> List[Any]: """模拟实现keys方法""" return [item[1] for item in self._data_array if item is not None] def values(self) -> List[Any]: """模拟实现values方法""" return [item[2] for item in self._data_array if item is not None] def items(self) -> List[Tuple[Any, Any]]: """模拟实现items方法""" return [(item[1], item[2]) for item in self._data_array if item is not None] if __name__ == '__main__': customer_dict: CustomerDict = CustomerDict() customer_dict["demo_1"] = "a" customer_dict["demo_2"] = "b" assert len(customer_dict) == 2 del customer_dict["demo_1"] del customer_dict["demo_2"] assert len(customer_dict) == 0 for i in range(30): customer_dict[i] = i assert len(customer_dict) == 30 customer_dict_value_list: List[Any] = customer_dict.values() for i in range(30): assert i == customer_dict[i] for i in range(30): assert customer_dict[i] == i del customer_dict[i] assert len(customer_dict) == 0
以上がPython での Dict 実装の原理は何ですか?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

ホットAIツール

Undresser.AI Undress
リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover
写真から衣服を削除するオンライン AI ツール。

Undress AI Tool
脱衣画像を無料で

Clothoff.io
AI衣類リムーバー

AI Hentai Generator
AIヘンタイを無料で生成します。

人気の記事

ホットツール

メモ帳++7.3.1
使いやすく無料のコードエディター

SublimeText3 中国語版
中国語版、とても使いやすい

ゼンドスタジオ 13.0.1
強力な PHP 統合開発環境

ドリームウィーバー CS6
ビジュアル Web 開発ツール

SublimeText3 Mac版
神レベルのコード編集ソフト(SublimeText3)

ホットトピック









PHPとPythonには独自の利点と短所があり、選択はプロジェクトのニーズと個人的な好みに依存します。 1.PHPは、大規模なWebアプリケーションの迅速な開発とメンテナンスに適しています。 2。Pythonは、データサイエンスと機械学習の分野を支配しています。

PythonとJavaScriptには、コミュニティ、ライブラリ、リソースの観点から、独自の利点と短所があります。 1)Pythonコミュニティはフレンドリーで初心者に適していますが、フロントエンドの開発リソースはJavaScriptほど豊富ではありません。 2)Pythonはデータサイエンスおよび機械学習ライブラリで強力ですが、JavaScriptはフロントエンド開発ライブラリとフレームワークで優れています。 3)どちらも豊富な学習リソースを持っていますが、Pythonは公式文書から始めるのに適していますが、JavaScriptはMDNWebDocsにより優れています。選択は、プロジェクトのニーズと個人的な関心に基づいている必要があります。

DockerはLinuxカーネル機能を使用して、効率的で孤立したアプリケーションランニング環境を提供します。その作業原則は次のとおりです。1。ミラーは、アプリケーションを実行するために必要なすべてを含む読み取り専用テンプレートとして使用されます。 2。ユニオンファイルシステム(UnionFS)は、違いを保存するだけで、スペースを節約し、高速化する複数のファイルシステムをスタックします。 3.デーモンはミラーとコンテナを管理し、クライアントはそれらをインタラクションに使用します。 4。名前空間とcgroupsは、コンテナの分離とリソースの制限を実装します。 5.複数のネットワークモードは、コンテナの相互接続をサポートします。これらのコア概念を理解することによってのみ、Dockerをよりよく利用できます。

Pythonは、自動化、スクリプト、およびタスク管理に優れています。 1)自動化:OSやShutilなどの標準ライブラリを介してファイルバックアップが実現されます。 2)スクリプトの書き込み:Psutilライブラリを使用してシステムリソースを監視します。 3)タスク管理:スケジュールライブラリを使用してタスクをスケジュールします。 Pythonの使いやすさと豊富なライブラリサポートにより、これらの分野で優先ツールになります。

VSコードでは、次の手順を通じて端末でプログラムを実行できます。コードを準備し、統合端子を開き、コードディレクトリが端末作業ディレクトリと一致していることを確認します。プログラミング言語(pythonのpython your_file_name.pyなど)に従って実行コマンドを選択して、それが正常に実行されるかどうかを確認し、エラーを解決します。デバッガーを使用して、デバッグ効率を向上させます。

VSコードは、Microsoftが開発した無料のオープンソースクロスプラットフォームコードエディターと開発環境であるフルネームVisual Studioコードです。幅広いプログラミング言語をサポートし、構文の強調表示、コード自動完了、コードスニペット、および開発効率を向上させるスマートプロンプトを提供します。リッチな拡張エコシステムを通じて、ユーザーは、デバッガー、コードフォーマットツール、GIT統合など、特定のニーズや言語に拡張機能を追加できます。 VSコードには、コードのバグをすばやく見つけて解決するのに役立つ直感的なデバッガーも含まれています。

VSコードはPythonの書き込みに使用でき、Pythonアプリケーションを開発するための理想的なツールになる多くの機能を提供できます。ユーザーは以下を可能にします。Python拡張機能をインストールして、コードの完了、構文の強調表示、デバッグなどの関数を取得できます。デバッガーを使用して、コードを段階的に追跡し、エラーを見つけて修正します。バージョンコントロールのためにGitを統合します。コードフォーマットツールを使用して、コードの一貫性を維持します。糸くずツールを使用して、事前に潜在的な問題を発見します。

VSコード拡張機能は、悪意のあるコードの隠れ、脆弱性の活用、合法的な拡張機能としての自慰行為など、悪意のあるリスクを引き起こします。悪意のある拡張機能を識別する方法には、パブリッシャーのチェック、コメントの読み取り、コードのチェック、およびインストールに注意してください。セキュリティ対策には、セキュリティ認識、良好な習慣、定期的な更新、ウイルス対策ソフトウェアも含まれます。
