標籤:
- 5 Array-Based Sequence
- 5.2.1 referential arrays
- 5.2.2 compact arrays in python
- array.array
- 5.3 dynamic arrays and amortization
- 5.3.1 implementing a dynamic array
- 5.4 efficiency of python‘s sequence types
- constant-time operation
- 5.6 multidimensional data sets
5 Array-Based Sequence5.2.1 referential arrays
數組在記憶體中是連續的地址單元,而且每個單元的大小是相同的。對於python的list來說,裡面可以儲存不同長度的 字串或者其他元素,為了存下最長的那個字串,list必須給每個單元很大的空間,這樣實際上有很多單元只利用 了小部分的儲存,記憶體的利用率很低。實際上,python的list中每個單元只儲存了一個對象的引用,相當於 只儲存了它的地址,這樣所有對象的地址都是統一長度的.list中空的位置的引用指向None對象
5.2.2 compact arrays in python
string是字元數組,這與一般的list不同,list中儲存的是引用,而string中是直接儲存字元,所以可以說是 緊密的的數組。如果string中儲存的是unicode,則每個字元需要2bytes的空間。
與緊密數組相比,普通list需要更多的空間,比如我們想要儲存一百萬個64-bit整數,我們希望每個整數只使用64 bits 來儲存。實際上我們需要在數組中給每個整數儲存一個64位地址的引用,另外每個int類型在python中是14byte,所以 實際上每個整數用了18bytes來儲存。
array.array
from array import arrayprimes = array(‘i‘, [2,3,5,7])
array函數提供緊密數組,其中‘i‘代表integers,array()函數第一個參數是裡面元素的類型。
python中的ctype模組裡提供了類似c語言中的緊密數組。
5.3 dynamic arrays and amortization
建立一個低級的緊密數組的時候,必須聲明數組的大小,因為系統必須給數組分配連續的記憶體空間。
python中的list類則提供了一種可動態擴充的數組,比如可以隨時增加一個元素。在建立一個list的時候,實際上會 比它現在的長度分配多一點空間,用來給新增加的元素。如果原來預留的空間都使用完了,則list會重新向系統申請 新的空間,新的空間又比現在所有儲存的元素預留多一些空間。這種做法就跟螃蟹成長的過程不斷換殼一樣。
len(list)可以擷取當前list裡面存的元素個數,但不是系統真正分配給list的記憶體。sys.getsizeof(list)可以 list真正的bytes。
import sysdata = []for k in range(n): a = len(data) b = sys.getsizeof(data) print "length: {0:3d}; size in types: {1:4d}".format(a,b) data.append(None)
結果如下:
Length: 0;size in bytes: 72Length: 1;size in bytes: 104Length: 2;size in bytes: 104Length: 3;size in bytes: 104Length: 4;size in bytes: 104Length: 5;size in bytes: 136...
可以發現初始化一個空數組時已經分配了72bytes的空間,後面不斷增加元素之後每次增加32bytes。
5.3.1 implementing a dynamic array
要實現動態增長的數組,我們可以先用一個固定數組a來儲存,當a滿的時候,建立更大的數組b,先使得b[i]=a[i], 然後將a指向b,這時候就可以插入新的元素了。如何確定新數組b的容量比較合適?一種做法是取b的容量剛好是a的2倍
import ctypesclass DynamicArray: """a dynamic array class like a simplified python list""" def __init__(self): self._n = 0 self._capacity = 1 self._A = self._make_array(self._capacity) # low-level array def __len__(self): return self._n def __getitem__(self, k): if not 0 <= k < self._n: raise IndexError(‘invalid index‘) return self._A[k] def append(self, obj): if self._n == self._capacity: self._resize(2 * self._capacity) self._A[self._n] = obj self._n += 1 def _resize(self, c): """resize internal array to capacity c""" B = self._make_array(c) for k in range(self._n): B[k] = self._A[k] self._A = B self._capacity = c def _make_array(self, c): return (c * ctypes.py_object)()
5.4 efficiency of python‘s sequence types
constant-time operation
返回序列的長度只需要常數時間,因為序列中維護有這一資訊可以直接返回。同樣是常數時間的有下標訪問data[i]
字串拼接
如要把文檔中的所有字母字元取出組成一個字串, bad code:
letters = ‘‘for c in document: if c.isalpha(): letters += c
這段代碼是非常低效的。因為string類型是immutable的,每次執行letters += c,都要重新建立一個string,然後 對letters重新賦值,而每次建立一個字串的時間與該字串長度成線性關係,所以總共需要1+2+...+n = O(n*n)的時間。
一種改進的方法是使用一個list代替string拼接,最後再一次性拼接給string,時間是O(n)
temp = []for c in document: if c.isalpah(): temp.append(c)letters = ‘‘.join(temp)
注意最後一行‘‘.join(temp)只需要n的時間
實際上即使是每次對list進行append操作,雖然攤還時間是O(1),但是仍然可能需要多次動態擴建list,效率不如下面這種 使用comprehension syntax理解性文法。
letters = ‘‘.join([c for c in document if c.isalpha()])
或者連建立list的過程都不需要
letters = ‘‘.join(c for c in document if c.islpha())
因為string是immutable的,所以很多時候要對string進行操作的時候可以先講string轉化為list,然後對其進行修改, 操作完成之後再重新賦值給string。string轉為list方式如list(‘bird‘)可以得到[‘b‘, ‘i‘, ‘r‘, ‘d‘]. 反過來list轉為string則可以通過‘‘.join([‘b‘,‘r‘,‘i‘,‘d‘])
5.6 multidimensional data sets
二維數組通常也叫矩陣。python中可以用嵌套的list來實現。
建立二維矩陣的一個錯誤方法如data = ([0] * c ) * r,因為結果還是一個一維矩陣。
改進一下,用data = [[0] * 3] * 2來建立2 * 3矩陣,得到結果[[0,0,0],[0,0,0]],好像滿足要求。 但實際上這樣做還不行,因為裡面的兩個list實際上指向同一個list,修改其中一個會導致兩個都同時改變。
為了使裡面的每個子list都是相互獨立的,可以使用理解性文法來建立這樣的二維list。 data = [[0] * c for j in range(r)]
python資料結構學習筆記(五)