今日のクラスでは、リストから要素を取得することは、PythonでO(1)
であることを学びました。これはなぜですか?たとえば、4つのアイテムのリストがあるとします。
li = ["perry", 1, 23.5, "s"]
これらのアイテムは、メモリ内でサイズが異なります。したがって、li[0]
のメモリ位置を取得し、各要素のサイズの3倍を追加してli[3]
のメモリ位置を取得することはできません。では、要素を取得するためにリストを走査する必要なく、インタープリターはどのようにしてli[3]
がどこにあるのかを知るのでしょうか?
Pythonのリストは、ポインターの配列として実装されます1。したがって、リストを作成するときに実際に何が起こっているのか:
["perry", 1, 23.5, "s"]
あなたは実際に次のようなポインタの配列を作成しているということです:
[0xa3d25342, 0x635423fa, 0xff243546, 0x2545fade]
各ポインターはメモリ内のそれぞれのオブジェクトを「ポイント」し、文字列"perry"
はアドレス0xa3d25342
に保存され、番号1
は0x635423fa
などに保存されます。
すべてのポインターは同じサイズであるため、インタープリターcanは、実際にli[0]
のアドレスに要素のサイズの3倍を追加して取得しますli[3]
に保存されているポインターへ。
1 詳細については、 馬の口(GitHubのCPythonソースコード) を参照してください。
a = [...]
と言うとき、a
はPyObject
sへのポインターの配列を含むPyObject
へのポインターです。
a[2]
を要求すると、インタープリターは最初にリストのPyObject
へのポインターに従い、次に2
をその中の配列のアドレスに追加してから、そのポインターを返します。 a[0]
またはa[9999]
を要求した場合も同じことが起こります。
基本的に、すべてのPythonオブジェクトは、2
などの整数リテラルでさえ、値ではなく参照によってアクセスされます。これをすべて効率的に保つためのポインターシステムには、いくつかのトリックがあります。サイズがわかっているため、Cスタイルの配列に便利に保存できます。
短い答え:Pythonリストは配列です。
長い答え:コンピューターサイエンスの用語listは、通常、一重リンクリスト(関数型プログラミングで使用)または二重リンクリスト(手続き型プログラミングで使用)を意味します。これらのデータ構造は、O(1)リストの先頭(機能的に)または検索する必要のない任意の位置(手続き的に)の挿入をサポートします。APython `` list ''にはこれらの特性はありません。代わりに(amortized)O(1) C++ std :: vectorのようにリストの最後に追加します)またはJava ArrayList)。Pythonリストは、CS用語では実際にサイズ変更可能な配列です。
次のコメント from Python documentation は、Python ``リスト ''のパフォーマンス特性の一部を説明しています:
リストをキューとして使用することもできます。追加される最初の要素は、最初に取得される要素です(「先入れ先出し」)。ただし、リストはこの目的には効率的ではありません。リストの末尾からの追加とポップは高速ですが、リストの先頭からの挿入またはポップは低速です(他のすべての要素には1つシフトします)。