5 Array-Based Sequence
5.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 array primes = 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 sys data = [] 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: 72 Length: 1;size in bytes: 104 Length: 2;size in bytes: 104 Length: 3;size in bytes: 104 Length: 4;size in bytes: 104 Length: 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 ctypes class 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
- tuple: nonmutating
- list
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)]