5. 数据结构

深入讲解之前一些内容同时增加知识
5.1.
列表详解

列表数据类型支持方法列表对象所有方法如下

list.append(x)

列表末尾添加类似 a[len(a):] = [x]。

list.extend(iterable)

通过添加来自 iterable 所有扩展列表类似 a[len(a):] = iterable。

list.insert(i, x)

指定位置插入元素第一参数插入元素索引因此,a.insert(0, x) 列表开头插入元素, a.insert(len(a), x) 等同 a.append(x) 。

list.remove(x)

列表删除第一 x 元素找到指定元素触发 ValueError 异常

list.pop([i])

移除列表给定位置条目返回条目如果指定索引 a.pop() 移除返回列表中的最后条目如果列表索引列表索引范围之外引发 IndexError。

list.clear()

移除列表中的所有类似 del a[:]。

list.index(x[, start[, end]])

返回列表第一 x 元素索引找到指定元素触发 ValueError 异常

可选参数 start end 切片符号用于搜索限制列表特定序列返回索引对于整个序列开始计算不是 start 参数

list.count(x)

返回列表元素 x 出现次数

list.sort(*, key=None, reverse=False)

就地排序列表中的元素了解自定义排序参数详见 sorted())。

list.reverse()

翻转列表中的元素

list.copy()

返回列表拷贝类似 a[:]。

多数列表方法示例
>>>

fruits = ['orange', 'apple', 'pear', 'banana', 'kiwi', 'apple', 'banana']

fruits.count('apple')
2

fruits.count('tangerine')
0

fruits.index('banana')
3

fruits.index('banana', 4) #
4 开始查找下一个 banana
6

fruits.reverse()

fruits
['banana', 'apple', 'kiwi', 'banana', 'pear', 'apple', 'orange']

fruits.append('grape')

fruits
['banana', 'apple', 'kiwi', 'banana', 'pear', 'apple', 'orange', 'grape']

fruits.sort()

fruits
['apple', 'apple', 'banana', 'banana', 'grape', 'kiwi', 'orange', 'pear']

fruits.pop()
'pear'

可能已经注意 insert, remove sort 修改列表方法不会打印返回 -- 它们返回默认 None。 [1] 适用 Python 所有可变数据结构设计原则

可能注意另一并非所有数据可以排序比较举例来说,[None, 'hello', 10] 不可排序因为整数不能字符串比较 None 不能其他类型比较此外存在一些没有定义顺序关系类型例如,3+4j < 5+7j 不是合法比较
5.1.1.
列表实现堆栈

列表方法使得列表用作非常容易最后添加元素最先取出(“后进先出”)。 条目添加栈顶使用 append()。 栈顶取出条目使用 pop() 不必指定索引例如:
>>>

stack = [3, 4, 5]

stack.append(6)

stack.append(7)

stack
[3, 4, 5, 6, 7]

stack.pop()
7

stack
[3, 4, 5, 6]

stack.pop()
6

stack.pop()
5

stack
[3, 4]

5.1.2.
列表实现队列

列表可以用作队列最先加入元素最先取出(“先进先出”);然而列表作为队列效率因为列表末尾添加删除元素非常列表开头插入移除元素因为所有其他元素必须移动)。

实现队列最好 collections.deque,可以快速两端添加删除元素例如
>>>

from collections import deque

queue = deque(["Eric", "John", "Michael"])

queue.append("Terry") # Terry
到了

queue.append("Graham") # Graham
到了

queue.popleft() #
第一现在
'Eric'

queue.popleft() #
第二现在
'John'

queue #
到达顺序排列剩余队列
deque(['Michael', 'Terry', 'Graham'])

5.1.3.
列表推导

列表推导创建列表方式简洁常见用法序列迭代对象中的元素应用某种操作生成结果创建列表满足特定条件元素创建序列

例如创建平方列表
>>>

squares = []

for x in range(10):

squares.append(x**2)


squares
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

注意代码创建覆盖变量 x,变量循环结束仍然存在方法可以副作用计算平方列表

squares = list(map(lambda x: x**2, range(10)))

等价

squares = [x**2 for x in range(10)]

上面这种写法简洁易读

列表推导方括号包含以下内容表达式后面 for 子句然后多个 for if 子句结果表达式依据 for if 子句求值计算得出列表举例来说以下列表推导列表相等元素组合起来
>>>

[(x, y) for x in [1,2,3] for y in [3,1,4] if x != y]
[(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]

等价
>>>

combs = []

for x in [1,2,3]:

for y in [3,1,4]:

if x != y:

combs.append((x, y))


combs
[(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]

注意上面代码,for if 顺序相同

表达式元组例如 (x, y))必须加上括号
>>>

vec = [-4, -2, 0, 2, 4]

#
新建翻倍列表

[x*2 for x in vec]
[-8, -4, 0, 4, 8]

#
过滤列表排除负数

[x for x in vec if x >= 0]
[0, 2, 4]

#
所有元素应用函数

[abs(x) for x in vec]
[4, 2, 0, 2, 4]

#
元素调用方法

freshfruit = [' banana', ' loganberry ', 'passion fruit ']

[weapon.strip() for weapon in freshfruit]
['banana', 'loganberry', 'passion fruit']

#
创建包含 (数字, 平方) 2 元组列表

[(x, x**2) for x in range(6)]
[(0, 0), (1, 1), (2, 4), (3, 9), (4, 16), (5, 25)]

#
元组必须圆括号否则引发错误

[x, x**2 for x in range(6)]
File "<stdin>", line 1
[x, x**2 for x in range(6)]
^^^^^^^
SyntaxError: did you forget parentheses around the comprehension target?

#
使用 'for' 展平嵌套列表

vec = [[1,2,3], [4,5,6], [7,8,9]]

[num for elem in vec for num in elem]
[1, 2, 3, 4, 5, 6, 7, 8, 9]

列表推导可以使用复杂表达式嵌套函数
>>>

from math import pi

[str(round(pi, i)) for i in range(1, 6)]
['3.1', '3.14', '3.142', '3.1416', '3.14159']

5.1.4.
嵌套列表推导

列表推导中的初始表达式可以任何表达式甚至可以另一列表推导

下面这个 3x4 矩阵 3 长度 4 列表组成
>>>

matrix = [

[1, 2, 3, 4],

[5, 6, 7, 8],

[9, 10, 11, 12],

]

下面列表推导可以转置行列
>>>

[[row[i] for row in matrix] for i in range(4)]
[[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]

我们之前小节看到内部列表推导之后 for 上下文求值所以这个例子等价:
>>>

transposed = []

for i in range(4):

transposed.append([row[i] for row in matrix])


transposed
[[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]

反过来说等价
>>>

transposed = []

for i in range(4):

#
以下 3 实现嵌套列表

transposed_row = []

for row in matrix:

transposed_row.append(row[i])

transposed.append(transposed_row)


transposed
[[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]

实际应用最好内置函数替代复杂流程语句此时,zip() 函数好用
>>>

list(zip(*matrix))
[(1, 5, 9), (2, 6, 10), (3, 7, 11), (4, 8, 12)]

关于本行星号详细说明参见 解包实参列表
5.2. del
语句

可以索引不是列表移除条目使用 del 语句不同返回 pop() 方法。 del 语句用来列表移除切片清空整个列表之前我们通过列表赋值切片实现功能)。 例如:
>>>

a = [-1, 1, 66.25, 333, 333, 1234.5]

del a[0]

a
[1, 66.25, 333, 333, 1234.5]

del a[2:4]

a
[1, 66.25, 1234.5]

del a[:]

a
[]

del
可以用来删除整个变量
>>>

del a

此后引用 a 报错直到赋与另一)。后文介绍 del 其他用法
5.3.
元组序列

列表字符串共性例如索引切片操作数据类型 序列参见 序列类型 --- list, tuple, range)。随着 Python 语言发展其他序列类型加入其中介绍一种标准序列类型元组

元组多个逗号隔开组成例如
>>>

t = 12345, 54321, 'hello!'

t[0]
12345

t
(12345, 54321, 'hello!')

#
元组可以嵌套

u = t, (1, 2, 3, 4, 5)

u
((12345, 54321, 'hello!'), (1, 2, 3, 4, 5))

#
元组不可对象

t[0] = 88888
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment

#
它们可以包含可变对象

v = ([1, 2, 3], [3, 2, 1])

v
([1, 2, 3], [3, 2, 1])

输出元组圆括号标注这样才能正确解释嵌套元组输入圆括号可有可无不过经常必须如果元组表达式一部分)。允许元组中的单个元素赋值当然可以创建列表可变对象元组

虽然元组列表使用场景不同用途不同元组 immutable (不可),一般包含异质元素序列通过解包索引访问如果 namedtuples,可以属性访问)。列表 mutable (可变),列表元素一般同质类型迭代访问

构造 0 1 元素元组比较特殊为了适应这种情况句法有一些额外改变一对圆括号可以创建元组只有元素元组可以通过这个元素添加逗号构建圆括号只有的话不够明确)。丑陋但是有效例如
>>>

empty = ()

singleton = 'hello', # <--
注意末尾逗号

len(empty)
0

len(singleton)
1

singleton
('hello',)

语句 t = 12345, 54321, 'hello!' 元组打包 例子 12345, 54321 'hello!' 一起打包元组操作可以
>>>

x, y, z = t

称之为 序列解包 妥妥的适用右侧任何序列序列解包左侧变量右侧序列元素数量相等注意多重赋值其实只是元组打包序列解包组合
5.4.
集合

Python
支持 集合 这种数据类型集合重复元素组成无序容器基本用法包括成员检测消除重复元素集合对象支持合集交集对称差分数学运算

创建集合花括号 set() 函数注意创建集合只能 set(),不能 {},{} 创建字典小节介绍数据结构字典

以下一些简单示例
>>>

basket = {'apple', 'orange', 'apple', 'pear', 'orange', 'banana'}

print(basket) #
显示重复移除
{'orange', 'banana', 'pear', 'apple'}

'orange' in basket #
快速成员检测
True

'crabgrass' in basket
False

#
演示针对单词有的字母进行集合运算


a = set('abracadabra')

b = set('alacazam')

a # a
有的字母
{'a', 'r', 'b', 'c', 'd'}

a - b #
存在 a 存在 b 中的字母
{'r', 'd', 'b'}

a | b #
存在 a b 两者有的字母
{'a', 'c', 'r', 'd', 'b', 'm', 'z', 'l'}

a & b #
同时存在 a b 中的字母
{'a', 'c'}

a ^ b #
存在 a b 两者有的字母
{'r', 'd', 'b', 'm', 'z', 'l'}

列表推导 类似集合支持推导
>>>

a = {x for x in 'abracadabra' if x not in 'abc'}

a
{'r', 'd'}

5.5.
字典

另一常用 Python 内置数据类型 字典 (参见 映射类型 --- dict)。 字典其他编程语言可能称为联合内存联合数组”。 连续整数索引序列不同字典是以 索引可以任何不可类型字符串数字总是可以作为元组包含字符串数字元组可以作为如果元组直接间接包含任何可变对象不可以用作不能使用列表作为因为列表使用索引赋值切片赋值 append() extend() 方法进行原地修改

可以字典理解 集合字典必须唯一花括号 {} 用于创建字典一种初始化字典方式花括号输入逗号分隔字典输出方式

字典主要用途通过关键字存储提取 del 可以删除存在关键字存储关键字关联取代通过存在提取报错

字典执行 list(d) 操作返回字典所有列表插入次序排列排序使用 sorted(d))。检查字典是否存在使用关键字 in。

以下一些字典简单示例
>>>

tel = {'jack': 4098, 'sape': 4139}

tel['guido'] = 4127

tel
{'jack': 4098, 'sape': 4139, 'guido': 4127}

tel['jack']
4098

del tel['sape']

tel['irv'] = 4127

tel
{'jack': 4098, 'guido': 4127, 'irv': 4127}

list(tel)
['jack', 'guido', 'irv']

sorted(tel)
['guido', 'irv', 'jack']

'guido' in tel
True

'jack' not in tel
False

dict()
构造函数可以直接序列创建字典
>>>

dict([('sape', 4139), ('guido', 4127), ('jack', 4098)])
{'sape': 4139, 'guido': 4127, 'jack': 4098}

字典推导可以任意表达式创建字典
>>>

{x: x**2 for x in (2, 4, 6)}
{2: 4, 4: 16, 6: 36}

关键字比较简单字符串直接关键字参数指定便捷
>>>

dict(sape=4139, guido=4127, jack=4098)
{'sape': 4139, 'guido': 4127, 'jack': 4098}

5.6.
循环技巧

字典执行循环可以使用 items() 方法同时提取及其对应
>>>

knights = {'gallahad': 'the pure', 'robin': 'the brave'}

for k, v in knights.items():

print(k, v)


gallahad the pure
robin the brave

序列循环 enumerate() 函数可以同时取出位置索引对应
>>>

for i, v in enumerate(['tic', 'tac', 'toe']):

print(i, v)


0 tic
1 tac
2 toe

同时循环多个序列 zip() 函数可以其内元素一一匹配
>>>

questions = ['name', 'quest', 'favorite color']

answers = ['lancelot', 'the holy grail', 'blue']

for q, a in zip(questions, answers):

print('What is your {0}? It is {1}.'.format(q, a))


What is your name? It is lancelot.
What is your quest? It is the holy grail.
What is your favorite color? It is blue.

为了逆向序列进行循环可以循环正向序列然后调用 reversed() 函数
>>>

for i in reversed(range(1, 10, 2)):

print(i)


9
7
5
3
1

指定顺序循环序列可以 sorted() 函数改动序列基础返回重新序列
>>>

basket = ['apple', 'orange', 'apple', 'pear', 'orange', 'banana']

for i in sorted(basket):

print(i)


apple
apple
banana
orange
orange
pear

使用 set() 去除序列中的重复元素使用 sorted() set() 排序顺序循环遍历序列中的唯一元素
>>>

basket = ['apple', 'orange', 'apple', 'pear', 'orange', 'banana']

for f in sorted(set(basket)):

print(f)


apple
banana
orange
pear

一般来说循环修改列表内容创建列表比较简单安全
>>>

import math

raw_data = [56.2, float('NaN'), 51.7, 55.3, 52.5, float('NaN'), 47.8]

filtered_data = []

for value in raw_data:

if not math.isnan(value):

filtered_data.append(value)


filtered_data
[56.2, 51.7, 55.3, 52.5, 47.8]

5.7.
深入条件控制

while
if 条件句不只可以进行比较可以使用任意运算

比较运算 in not in 用于执行确定是否存在存在容器中的成员检测运算 is is not 用于比较对象是否同一对象所有比较运算优先级一样低于任何数值运算

比较操作支持操作例如,a < b == c 校验 a 是否小于 b, b 是否等于 c。

比较操作可以布尔运算 and or 组合并且比较操作其他布尔运算结果可以 not 这些操作符优先级低于比较操作符;not 优先级最高, or 优先级因此,A and not B or C 等价 (A and (not B)) or C。其他运算操作一样此处可以圆括号表示想要组合

布尔运算 and or 所谓 短路 运算参数左至右求值一旦可以确定结果求值停止例如如果 A C ,B 那么 A and B and C 不会 C 求值用作普通不是布尔短路运算返回通常最后参数

可以比较运算其它布尔表达式结果赋值变量例如
>>>

string1, string2, string3 = '', 'Trondheim', 'Hammer Dance'

non_null = string1 or string2 or string3

non_null
'Trondheim'

注意,Python C 不同表达式内部赋值必须使用 海象运算 :=。 避免 C 程序常见问题表达式 == 成了 =。
5.8.
序列其他类型比较

序列对象可以相同序列类型其他对象比较这种比较使用 字典 顺序首先比较对应元素如果相等确定比较结果如果相等比较之后元素以此类推直到其中序列结束如果比较元素本身相同类型序列递归执行字典顺序比较如果序列所有对应元素相等序列相等如果序列另一初始序列序列视为序列对于字符串字典顺序使用 Unicode 序号排序单个字符下面列出一些比较相同类型序列例子

(1, 2, 3) < (1, 2, 4)
[1, 2, 3] < [1, 2, 4]
'ABC' < 'C' < 'Pascal' < 'Python'
(1, 2, 3, 4) < (1, 2, 4)
(1, 2) < (1, 2, -1)
(1, 2, 3) == (1.0, 2.0, 3.0)
(1, 2, ('aa', 'ab')) < (1, 2, ('abc', 'a'), 4)

注意比较不同类型对象只要比较对象提供合适比较方法可以使用 < > 进行比较例如混合数字类型通过数字进行比较所以,0 等于 0.0,等等如果没有提供合适比较方法解释器不会随便比较结果而是引发 TypeError 异常

备注
[1]

别的语言可能可变对象返回允许方法连续执行例如 d->insert("a")->remove("b")->sort();。