回答:
これは動的配列です。実用的な証明:インデックス作成は、インデックスに関係なく(もちろん、ごくわずかな差異(0.0013 µ秒!))同じ時間を要します。
...>python -m timeit --setup="x = [None]*1000" "x[500]"
10000000 loops, best of 3: 0.0579 usec per loop
...>python -m timeit --setup="x = [None]*1000" "x[0]"
10000000 loops, best of 3: 0.0566 usec per loop
IronPythonまたはJythonがリンクされたリストを使用する場合、私は驚かれます-リストは動的配列であるという仮定に基づいて構築された多くの広く使用されているライブラリのパフォーマンスを台無しにします。
x=[None]*1000
、起こり得るリストアクセスの違いの測定値は不正確なままです。あなたは初期化を分離する必要があります:-s "x=[None]*100" "x[0]"
Cコードは実際にはかなり単純です。1つのマクロを展開し、無関係なコメントを削除すると、基本的な構造はになりlistobject.h
、リストは次のように定義されます。
typedef struct {
PyObject_HEAD
Py_ssize_t ob_size;
/* Vector of pointers to list elements. list[0] is ob_item[0], etc. */
PyObject **ob_item;
/* ob_item contains space for 'allocated' elements. The number
* currently in use is ob_size.
* Invariants:
* 0 <= ob_size <= allocated
* len(list) == ob_size
* ob_item == NULL implies ob_size == allocated == 0
*/
Py_ssize_t allocated;
} PyListObject;
PyObject_HEAD
参照カウントとタイプ識別子が含まれています。だから、それはoverococateするベクトル/配列です。このような配列がいっぱいになったときにサイズを変更するコードはにありlistobject.c
ます。実際には配列を2倍にするわけではありませんが、
new_allocated = (newsize >> 3) + (newsize < 9 ? 3 : 6);
new_allocated += newsize;
毎回容量に、newsize
は要求されたサイズです(要素を1つずつではなく、任意の数の要素を使用allocated + 1
できるためとは限りません)。extend
append
Python FAQも参照してください。
array
モジュールまたはNumPyが推奨されます。
これは実装に依存しますが、IIRC:
ArrayList
したがって、それらはすべてO(1)ランダムアクセスを持っています。
O(1)
リストのインデックス作成はかなり一般的で有効な前提であるため、あえてそれを壊すような実装はありません。
Laurent Luceの記事「Pythonリストの実装」をお勧めします。著者がリストがCPythonでどのように実装されているかを説明し、この目的のために優れた図を使用しているので、私にとって本当に役に立ちました。
オブジェクトC構造体のリスト
CPythonのリストオブジェクトは、次のC構造体で表されます。
ob_item
リスト要素へのポインタのリストです。allocationは、メモリに割り当てられたスロットの数です。typedef struct { PyObject_VAR_HEAD PyObject **ob_item; Py_ssize_t allocated; } PyListObject;
割り当てられたスロットとリストのサイズの違いに注意することが重要です。リストのサイズはと同じ
len(l)
です。割り当てられたスロットの数は、メモリに割り当てられた数です。多くの場合、割り当てられたサイズよりも大きいことがわかります。これはrealloc
、新しい要素がリストに追加されるたびに呼び出す必要がないようにするためです。
...
追加
リストに整数を追加します
l.append(1)
。何が起こるのですか?
続いて、もう1つの要素を追加します
l.append(2)
。list_resize
n + 1 = 2で呼び出されますが、割り当てられたサイズが4であるため、さらにメモリを割り当てる必要はありません。我々は2つの以上の整数を追加するときに同じことが起こります:l.append(3)
、l.append(4)
。次の図は、これまでのところを示しています。
...
インサート
...
ポップ
最後の要素をポップすると
l.pop()
、listpop()
が呼び出されます。list_resize
内部で呼び出されlistpop()
、新しいサイズが割り当てられたサイズの半分未満の場合、リストは縮小されます。スロット4がまだ整数を指していることを確認できますが、重要なのは、現在は4であるリストのサイズです。要素をもう1つポップしましょう。では
list_resize()
、サイズ– 1 = 4 – 1 = 3は割り当てられたスロットの半分未満なので、リストは6スロットに縮小され、リストの新しいサイズは3になります。スロット3と4がまだいくつかの整数をポイントしていることを確認できますが、重要なのは、現在は3であるリストのサイズです。
...
aggregation
はなく、composition
です。作曲のリストもあったらいいのに。
他の人が上で述べたように、リストは(かなり大きい場合)固定量のスペースを割り当てて実装され、そのスペースがいっぱいになる場合は、より多くのスペースを割り当てて要素をコピーします。
メソッドが一般性を失うことなくO(1)で償却される理由を理解するために、a = 2 ^ n要素を挿入し、テーブルを2 ^(n + 1)サイズに2倍にする必要があると仮定します。つまり、現在2 ^(n + 1)演算を実行しています。最後のコピーでは、2 ^ n回の操作を行いました。その前に、2 ^(n-1)... 8、4、2、1までずっと計算しました。これらを合計すると、1 + 2 + 4 + 8 + ... + 2 ^(n + 1)= 2 ^(n + 2)-1 <4 * 2 ^ n = O(2 ^ n)= O(a)合計挿入数(つまり、O(1)償却時間)。また、テーブルで削除が許可されている場合、テーブルの縮小は別の係数(3倍など)で行う必要があることに注意してください。
Pythonのリストは、複数の値を格納できる配列のようなものです。リストは変更可能であるため、変更できます。さらに重要なことは、リストを作成すると、Pythonはそのリスト変数のreference_idを自動的に作成するということです。他の変数を割り当てて変更すると、メインリストが変更されます。例で試してみましょう:
list_one = [1,2,3,4]
my_list = list_one
#my_list: [1,2,3,4]
my_list.append("new")
#my_list: [1,2,3,4,'new']
#list_one: [1,2,3,4,'new']
追加しましたmy_list
が、メインリストが変更されました。つまり、リストは参照として割り当てられたコピーリストとして割り当てられませんでした。