第五章数据结构 python导引编译之六

标题5 数据结构Data Structures

这一章描述了某些你已经学过的内容,但会描述得更为详细,也增加了一些新的内容。

标题5.1.有关列表的更多内容

这个列表数据类型有更多的方法,这里是有关列表对象的所有方法

列表增加项目append(x)
增加一个项目给给列表的末端。这个方法等价于a[len(a)]=[x]。

列表扩充(迭代)list.extend(iterable)
通过给列表添加来自可迭代的所有项目来扩充列表。该方法等价于a[len(a)] = 可迭代。

列表插入list.insert(i, x)
在给定位置插入一个项目。第一个参数是 将插入的那个参数之前元素的指数,所以,指令insert(0,x)就是在列表最前面的位置插入x,并且,一个指令insert((len(a),x)等价于指令append(x)。

列表移动list remove(x)
从列表中移除其值等价于x的第一个项目。若没有这样一个项目,则会出现赋值错误ValueError。

列表删除list pop([i])
移除列表中给定位置的那个项目,并且返回这个项目。如果没有指定位置,一个指令pop()就移除并且返回列表中的最后一个项。(该方法中环绕着字母i的方括弧,指谓参数是可选的,并不是指谓你应该在给定位置打上方括弧。在python的图书馆参考资料栏,你会经常看到这个记号。

列表清除list.clear()
移除列表中的所有项目。这个指令等价于del a[:]。

列表索引list.index(x[, start[, end]])
在其值等于x的第一个项目的列表中,返回从零开始的索引。如果没有这样一个项目,则会产生赋值错误ValueError。
可选参数在切片记号中被解释为开始与结束,这两个参数也被用来限定对于特定列表序列进行搜寻。返回索引相关于全序列的起点,而非开始参数被计算。

列表计数list.count(x)
返回出现在列表中的x的倍数。

列表分类list.sort(key=None, reverse=False)
给放置在列表中的项目分类(参数可以为分类定制而被使用,对于这个指令的解释,请看指令sorted()。

列表反转list.reverse()
反转放置在列表中的元素。

列表复制list.copy()
返回该列表的一个浅复制。等价于a[:]。

以上列表方法中大多数方法的一个应用实例:

Type "help", "copyright", "credits" or "license" for more information.
>>> fruits = ['orange', 'apple',  'pear', 'banana', 'kiwi', 'apple', 'banana', 'apple']
>>> fruits.count('apple') # 计数
3
>>> fruits.count('tiger')
0
>>> fruits.index('banana') #指定项目在列表中的位置数,从0起点计算。
3
>>> fruits.index('banana',4)  # 从位置4开始,寻找下一个banana。
6
>>> fruits.reverse() #将列表序列翻转过来。
>>> fruits
['apple', 'banana', 'apple', 'kiwi', 'banana', 'pear', 'apple', 'orange']
>>> fruits.append('grape') #给列表增加一个项目。
>>> fruits
['apple', 'banana', 'apple', 'kiwi', 'banana', 'pear', 'apple', 'orange', 'grape']
>>> fruits.sort() # 给列表中的项目分类。
>>> fruits
['apple', 'apple', 'apple', 'banana', 'banana', 'grape', 'kiwi', 'orange', 'pear']
>>> fruits.pop()
'pear'
>>>

你也许注意到,像insert,remove,或者分类sort这样的方法仅仅修改了列表,但这些列表没有返回值打印出来–它们返回的是缺省值None[1]。在python中,对于所有易于变化的数据结构,这是一个设计的原则。
你也许注意到的另一个东西是,并非所有的数据都可以分类或者可以进行比较。例如,一个这样的列表[None, ‘hello’, 10]无法分类,因为整数无法对于字符串进行比较,也不能够用其它的类型进行比较。还有一些没有定义为序列关系的类型。例如,3+4j<5+7j,这样的表达式不是一个有效的比较。

标题5.1.1.把列表看作为堆using lists as stacks

列表的那些方法很容易让列表作为堆来使用,在列表中加进的那个最后的元素,它也是找回的第一个元素(最后进,第一个出)。给堆的顶部增加一个元素,使用append()。从堆的顶部找回一个项,无需使用一个明确的指数,用pop()就行。例如:

stack = [3, 4, 5]
stack.append(6)
stack.append(7)
stack.append(8)
stack
[3, 4, 5, 6, 7, 8]

stack.pop()
8

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

stack.pop()
7

stack.pop()
6

stack
[3, 4, 5]

标题5.1.2.把列表当作序列 Using Lists as Queues

把列表当作序列也是可能的,其中的第一个被加进的元素也是第一个被找回的元素(第一个进,第一个出);然而,列表还不能完全地完成这个目标。当来自列表末端的添加append和删除留一pop是快快的,来自列表起点的插入insert或者删除pops则是慢慢的。(因为所有其它元素必须被一种所转换)。
为了完备地做完一个序列,使用指令集中队列collection.deque。这个指令是为从两端快速地增加和删除而设计的。例如:

from collections import deque
queue = deque([“Eric”, “John”, “Michael”])
queue.append(“Terry”) # 又来了一个Terry
queue.append(“Graham”) #接着还来了一个Graham
queue.popleft() #去掉queue中的第一个
‘Eric’

queue.popleft() # 去掉剩下的第一个
‘John’

queue
deque([‘Michael’, ‘Terry’, ‘Graham’])
``

标题5.1.3. 列表综合List Comprehensions

列表综合这一节,给我们提供了一种简明的方式去创建列表。 通常的应用是构成一些新列表,这些列表中,每一个元素都是进行某些操作的结果,而这些操作指的是应用到另一个序列或者迭代的每一个成员而获得的结果,或者是创建一个那些元素的子序列,这些序列满足某种条件。
例如,假定我们想要创建一个平方列表:


>>> squares = [] #创建一个平方列表
>>> for x in range(10):
...     squares.append(x**2)
...
>>> squares
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

注意,这创建了一个名为x的变元,该变元在for循环完成之后依然存在。我们可以计算这个平方列表,无需任何别的手段。

>>> squares = list(map(lambda x: x**2, range(10)))
>>> squares = [x**2 for x in range(10)]

这两个平方列表是等价的,但后一个更为简洁,更有可读性。
一个列表综合由一个方括弧,加上括弧内的一个表达式构成,该表达式后随一个循环for所带的一个从句,接之有零个或者多个for或者if从句。结果将会是一个新列表。那个新列表是给跟随其后的for循环或者if从句范围内的表达式赋值而获得的结果。例如,这个列表合成listcomp,结合了两个列表的元素,如果这两个列表不是等同的话。
以下代码中两个列表是等价的

Type "help", "copyright", "credits" or "license" for more information.
>>> squares = list(map(lambda x: x**2, range(10)))
>>> squares = [x**2 for x in range(10)]
>>> [(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 in vec]
  File "<stdin>", line 1
    [x*2 for in vec]
             ^
SyntaxError: invalid syntax
>>> [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']
>>> #创建一个两元组的列表,如同(数字, 平方)
>>> [(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: invalid syntax
>>> # 使用列表综合,用两个循环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. 嵌套列表综合Nested List Comprehensions

在一个列表综合中的初始表达式可以是任何随意的表达式,包括另一个列表综合。
考虑以下3x4矩阵的例子,它可以操作为长度为4的三个列表:

matrix = [
… [1, 2, 3, 4]
… [5, 6, 7, 8]
… [9, 10, 11, 12]
…]

以下列表综合将转变为竖行和横行:

>>> 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):
... #以下三行就是在做嵌套的列表合成
...     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.删除陈述 The del statement

从一个给定其索引而不是给定其值列表中移除一个项,有一个方法:那就是删除陈述the del statement。这不同于删除留一的pop(),它会返回一个值。这个删除陈述del statement也可以用作从一个列表中移除切片,或者用来清除全部列表(我们早期通过对一个空列表到切片的指派所做的)。例如:

Python 3.8.3 (tags/v3.8.3:6f8c832, May 13 2020, 22:37:02) [MSC v.1924 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> 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, 333, 1234.5]
>>> del a[:]
>>> a
[]

删除del指令也可以用来删除掉整个列表

>>> del a

此后再提及到一个名称a,那就是一个谬误(至少不到另一个值指派给这个a为止)。后面,我们将发现对于删除del的另一些用法。

标题5.3. 元组与序列Tuples and Sequences

我们看到,列表与字符串有许多共同的性质,例如索引和切片操作。它们 是序列数据的两个实例(看序列类型—列表 元组和域)。因为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])

如同我们看到的,那些打印出的元组总是用括号封闭起来,以便嵌套的元组得到正确的解释:这些元组也许用括号,也许不用括号,虽然括号无论何时总是必要的(如果那些元组是一个更大表达式的一部分)。想给元组中的个体元素做指派那是不可能的,然而,却有可能创建这样的元组,它们含有易变的对象,例如列表。
虽然元组非常类似于列表,但它们常被用在不同的情景,并且出于不同的目的所用。元组是不可改变的,而且,通常含有一个多样化的元素序列,这些序列是通过解包而获得的(解包指令看本节后面)或者通过索引而获得(或者借助在名称元组namedtuples情形中具有的属性而获得)。列表是可以变化的,列表的元素通常也是多种多样,并且,可借助迭代列表而获得。
一个特殊的问题是,含有0或者1的元组结构:句法学有某种怪癖接纳这样的元组。空元组用一个空括弧来构成;而仅有一个项的元组是使用一个逗号,在逗号的前面带有一个项(在括号中封闭单一的值,还不是完整的元组标识,必须在单值之后带有逗号)。看起来不美很丑,但却有效。例如:

>>> empty = ()
>>> singleton = 'hello', #注意,<-- 尾随的逗号
>>> len(empty)
0
>>> len(singelton)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'singelton' is not defined
>>> len(singleton)
1
>>> singleton
('hello',)

陈述 t = 12345, 54321, ‘hello!’,这是元组打包的一个例子,三个值打包在一起构成了一个元组。但反过来的操作也是可以的:

>>> x, y, z = t

这相当充分地可以称做序列解包,并且对于在右手边的任意序列也适合。序列解包要求,等号左边的变元数量要和序列中的元素数量一样多。注意多元指派,实际上恰恰是打包的元组和解包的序列的一个组合。

标题5.4. 集合Sets

python也包括一个表示集合的数据类型。一个集合是一个没有完全相同元素的无需组合。基本的运用包括了成员测试和消除复制项。集合对象也支持类似并、交、差和对称差那样的数学运算。
卷曲的吊带也就是大括弧curly braces或者函数set()可用来创建集合。注意:你要创建一个空集合,你就必须使用set()这样的表达式,而不是set{};后者创建的是一个空词典,一个我们将在下一节讨论的数据结构。
这里是一个简要的集合显示:

>>> basket = {'apple', 'orange', 'apple', 'pear', 'orange', 'banana'}
>>> print(basket) #显示出复制l项已经被移除
{'banana', 'orange', 'pear', 'apple'}
>>> 'orange' in basket #测试成员是否属于集合
True
>>> 'berry' in basket
False
>>>
>>> #显示来自两个单词的在仅有字母上的集合操作
>>> a = set('abracadabra')
>>> b = set('alacazam')
>>> a #在a中出现的字母
{'c', 'd', 'r', 'b', 'a'}
>>> b
{'m', 'c', 'z', 'l', 'a'}
>>> a - b #a中出现但b中不出现
{'r', 'b', 'd'}
>>> a | b #或在a中或在b中
{'m', 'c', 'z', 'l', 'd', 'r', 'b', 'a'}
>>> a & b #既在a中又在b中
{'c', 'a'}
>>> a ^ b
{'m', 'z', 'r', 'b', 'l', 'd'}

类似于列表综合,集合也可以使用集合综合

>>> a = {x for x in 'abracadabra' if x not in 'abc'}
>>> a
{'d', 'r'}

标题5.5.字典 Dictionaries

内置于python中的另一个有用数据类型是字典(看映射类型mapping types–dict)。字典有时候在另外的语言如同联想记忆或者联想阵列那样被发现。不像序列,序列是被一个数字范围索引,而字典是被关键字所索引,关键字可以是任意的不可改动的类型;字符串和数字总是可以成为关键字。如果元组中含有字符串,数字或者含有元组,则这样的元组也可以用作关键字。但如果一个元组含有任意易变化对象,直接地含有或者间接地含有,则这样的元组不能够作为关键字。你也不能用列表来做关键字,因为列表在使用索引指派、切片指派或者类似于append()和extend()指令的地方可以被修改。
最好把字典看作是一个关键字:赋值这样一个对偶的集合,并伴随这样的要求,关键字在一个字典之内是唯一的。一个吊带样大花括弧,什么也没有框住的对偶,创建了一个空字典:{ }。键值对偶的列表如果不空,在花括弧内添加初始的键值对偶,然后用一个逗号把其后添加的对偶分离开。这也是字典编辑或者输出的方式。
基于一个字典的主要运算是在储存一个带有关键字的值,给定那个关键字就可以抽象出对应的值。删除一个键值对偶也是可能的,那就是使用指令del。如果你储存你已经在使用的一个关键字,那么那个原先与关键字相联系的旧的值就被忘却。因此,使用一个并不存在的关键字来抽象出它的对偶值,那就是一个错误。
在一个字典上操作指令list(d),可以返回在字典上用过的,在插入次序上用过的所有关键字列表(如果你想字典分类,正好使用指令sorted(d))你想检查一个单一的关键字是否就在字典中,使用指令in这个关键字。
这里是一个小小的使用一个字典的例子:

>>> tel = {'jack': 4098, 'sape': 4139}
>>> tel['quido'] = 4127
>>> tel
{'jack': 4098, 'sape': 4139, 'quido': 4127}
>>> tel['jack']
4098
>>> del tel['sape']
>>> tel['irv'] = 4127
>>> tel
{'jack': 4098, 'quido': 4127, 'irv': 4127}
>>> sorted(tel)
['irv', 'jack', 'quido']
>>> 'quido' 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.循环技巧 Looping Techniques

当循环通过字典的时候,关键字和对应的值在运用函数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', 'bule']
>>> 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 bule.

为了循环一个反转的序列,先指定系列朝向的方向,然后调用反转函数reversed()。

for i in reversed(range(1, 10)):
… print(i)

9
8
7
6
5
4
3
2
1```

为了循环一个在分类次序函数中的序列,使用分类函数,在留下未改变的源文件的同时,也返回一个新的分类列表。

>>> 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.有关条件的更多内容 More on Conditions

在while和for这两种循环陈述中的条件,可以含有任意运算,不仅仅是比较。
比较运算子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。当作为一般赋值而使用时,和并不是作为布尔式来赋值的时候,一个短回路的运算子的返回值就是那个最后赋值的参数。
指派一个比较式的结果,或者其它布尔表达式对于一个变元的结果,这是可能的。例如。

>>> non_null = string1 or string2 or string3
>>> non_null
' '
>>> non_null(1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'str' object is not callable
>>> string1, string2, string3 = 'Trondheim', '', 'Hammer Dance'
>>> non_null = string1 or string2 or string3
>>> non_null
'Trondheim'

注意,在python中,不像C语言,内部表达式中的指派,必须用海象运算子:=明确地做到。这避免了在C语言程序中所面对问题的普通类:当双等号==被要求的时候,在一个表达式中打出那个=。

标题5.8. 比较序列和其它类型Comparing Sequences and Other Types

序列对象典型地可能要和同样的序列类型的其它对象进行比较。这种比较使用词典顺序来进行:首先是头两个项目进行比较,如果它们有差异,这就判定了比较的结果;如果它们相同,那就进行下两项的比较,直到序列的全部项目都被比较过。如果两个被比较的项目是属于同样类型的序列,就是它们自身,这个词典顺序式的比较就将递归式的进行。如果两个序列的所有项目比较等同,该序列就要考虑是等同的。如果一个序列是另一个序列的初始子序列,则那个较短的序列就是较小的一个。字符串的词典顺序式次序使用通用码的点数给个体字符排出顺序。相同类型的序列之间的比较实例如下:

(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()。

猜你喜欢

转载自blog.csdn.net/weixin_41670255/article/details/108893133
今日推荐