forked from xingkeyu/byte_of_python
-
Notifications
You must be signed in to change notification settings - Fork 0
/
11-data-structures.pd
420 lines (283 loc) · 19.5 KB
/
11-data-structures.pd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
# 数据结构
数据结构基本上是由于--他们是*结构*可将一些*数据*容纳在一起。换句话说,它们是用来存储一系列关的数据。
在Python中有四种 内建数据结构--列表、元组、字典和集合(set),我们将看到如何使用它们中的每一个,它们是怎样使我们的生活更容易的。
## 列表
`列表`是一种数据结构,它保存条目的有序集合。例如,你可以在列表中存储一个*序列*。这很容易想象,你想像一下购物清单,那里有你要购买物品的一个清单。除非在你的清单上每一行列有一个单独物品,然而,在python中,你在它们中间放上逗号。
条目的列表应包含在方括号内,以便Python明白你在指定一个列表。一旦您创建了一个列表,你可以添加、删除或是搜索列表中的条目。因为我们可以添加和删除条目,我们说一个列表是一个*可变的*数据类型,即这种类型可以更改。
### 对象和类的快速介绍
尽管直到现在,我一直推迟讨论对象和类,现在需要一个小小的解释,这样你可以更好的理解列表。我们将在[后面章节](#面向对象的程序设计)中详细探讨这一课题。
列表是使用对象和类的一个例子。当我们使用一个变量 `i`,为它分配一个值,例如把整数`5`赋值给它,你可以认为它是创建一个**类**为`int`(即类型)的*对象*(即实例)`i`。事实上,你可以阅读的`help(int)`更好地理解这一点。
类也有**方法**,也就是为了使用而定义的只关于那个类的函数,只有当你有那个类的对象时,你才可以使用这些函数.例如,Python为`list`(列表)类提供了一个`append`方法,它允许你在列表的整改添加一个条目。例如,`mylist.append('an item')`将给列表`mylist`添加字符串。注意,我们使用点操作符访问对象的方法。
类也有**字段**,除了为了使用而定义的只与那个类相关的变量,它什么也没有。只有当你有那个类的对象时,你可以使用那些变量或名字。字段孔明通过点操作符访问的。例如,`mylist.field`。
例子 (保存为`using_list.py`):
~~~python
# 这是我的购物清单
shoplist = ['苹果', '芒果', '胡萝卜', '香蕉']
print('我要买', len(shoplist), '个物品。')
print('清单是:', end=' ')
for item in shoplist:
print(item, end=' ')
print('\n我还要买大米。')
shoplist.append('大米')
print('现在我的清单是', shoplist)
print('现在我将要为我的清单排序')
shoplist.sort()
print('排序后的购物清单是', shoplist)
print('我要买的第一个物品是', shoplist[0])
olditem = shoplist[0]
del shoplist[0]
print('我已经买了', olditem)
print('现在我的清单是', shoplist)
~~~
输出:
~~~
$ python3 using_list.py
我要买 4 个物品。
清单是: 苹果 芒果 胡萝卜 香蕉
我还要买大米。
现在我的清单是 ['苹果', '芒果', '胡萝卜', '香蕉', '大米']
现在我将要为我的清单排序
排序后的购物清单是 ['大米', '胡萝卜', '芒果', '苹果', '香蕉']
我要买的第一个物品是 大米
我已经买了大米
现在我的清单是 ['胡萝卜', '芒果', '苹果', '香蕉']
~~~
(注:原文为英文,翻译为中文排序后结果与英文不一致,其后的运行结果也不一样了)
它是如何工作的:
变量`shoplist`是将要去超市的人的一个购物清单。在`shoplist`中,我们只存储了要买的物品的名字的字符串,你可以向清单中添加包括数字甚至是其它清单的*任何对象*。
我们也使用了循环`for..in`遍历清单中的所有条目。到现在为止,你必须认识到一个清单也是一个序列。序列的特性将在[后面的章节](#序列)中讨论。
注意,在`print`函数中使用`end`关键字参数,表明输出以一个空格结束而不是通常的换行。
接下来,和前面讨论过的一样,我们使用列表对象的`append`方法向列表中添加一个项目。然后,我们只把列表简单地传递给`print`语句,整洁地打印列表的内容,以检查这个条目确实添加到了列表中。
然后,我们使用列表对象的`sort`方法为列表排序。 这个方法作用到列表本身,并不返回一个修改过的列表,理解这一点很很重要,它不同于对字符串的操作。这也是为什么我们列表是*可修改的*,而字符串是*不可修改的*原因。
然后,我们在超市购买了一个物品,我们想把它从购物清单中移除,通过使用`del`语句来实现。这里,我们提到我们想要移除清单中的哪个物品,`del`语句为我们将它从清单中移除。我们指定,我们想从清单移除第一项,因此,我们使用`del shoplist[0]`(记住,Python从0开始数数数)。
如果你想知道列表对象定义的所有方法,详见`help(list)`。
## 元组
元组是用来容纳多个对象。认为它们是类似于列表,但是没有列表给你的广泛功能。元组的一个主要特征是他们是**不可变**,像字符串,即您不能修改元组。
元组是通过在一对可选的圆括号中,项目之间用逗号分隔来定义的。
元组通常用在,一个语句或一个用户定义的函数能够安全地假设为值的集合,即值的元组,不会改变。
例子 (保存为`using_tuple.py`):
~~~python
zoo = ('蟒蛇', '大象', '企鹅') # 记住圆括号是可选的
print('动物园中动物有数量有', len(zoo))
new_zoo = '猴子', '骆驼', zoo
print('在新动物园中笼子的数量是', len(new_zoo))
print('在新动物园所有的动物是', new_zoo)
print('从老动物园中带来的动物是', new_zoo[2])
print('从老动物园带来最后的动物是', new_zoo[2][2])
print('在新动物园中动物的数量有', len(new_zoo)-1+len(new_zoo[2]))
~~~
输出:
~~~
$ python3 using_tuple.py
动物园中动物有数量有 3
在新动物园中笼子的数量是 3
在新动物园所有的动物是 ('猴子', '骆驼', ('蟒蛇', '大象', '企鹅'))
从老动物园中带来的动物是 ('蟒蛇', '大象', '企鹅')
从老动物园带来最后的动物是 企鹅
在新动物园中动物的数量有 5
~~~
它是如何工作的:
变量`zoo`指的是一个物品的元组。我们看到`len`函数可以用来获取元组的长度。这也表明,一个元组同样也是一个(序列)(#序列)。
因为老动物园zoo将要关闭,我们现在将这些动物迁移到一个新的动物园new_zoo。因此,(新动物园)`new_zoo`的tuple包含一些已经存在的动物以及从老动物园zoo带来的动物。回到现实,请注意,在一个元组中的元组不失去其特性。
就像列表一样,我们可以通过在一对方括号中指定条目的位置,访问元组中的物品。这被称为*索引*操作符。我们通过指定`new_zoo[2]`访问新动物园new_zoo中的第三项,通过指定new_zoo[2][2]`访问新动物园new_zoo的第三项。一旦理解这个习语,这是非常简单的。
圆括号
: 尽管括号是可选的,我总是使用它们以便使它是一个元组更明显,特别是因为,它避免歧义。例如`print(1,2,3)`和print( (1,2,3) )`两件不同意思的事情——前者打印3个数,而后者输出一个元组(包含三个数字)。
有0个或1个条目的元组
: 一个空的元组由一对空的括号如`myempty = ()`组成。 然而,只有一个对象的元组并非如此简单。你必须通过在第一个对象(唯一的一个)后紧跟一个逗号来指定它,这样Python可以区分是一个元组还是一个表达式中一个对象的括号,例如,如果你想定义一个只含一个对象这为`2`的元组,你必须使用 `singleton = (2 , )`。
Perl程序员应该注意
: 在一个列表中的列表不会失去其特性,也就是说并不像在Perl中夷为平地。这同样适用于在一个元组中的一个元组,或在一个列表中的元组,或在一个元组中的列表等。就Python而言,他们只是存储在另一个对象中的一个对象。
## 字典
字典就像一个地址簿,在那里你只通过知道他/她的名字,就可以找到地址或联系人详细信息。也就是说,我们使用**键*(姓名)与**值**(细节)相联系。注意,键必须是独一无二的,就像如果有两个完全相同的名字的人,你无法找到正确的信息。
注意,字典的关键字你只能使用不可变的对象(比如字符串),你可以使用不可变或可变的对象作为字典的值。这基本上意味着,简单地说,对于键你只能使用简单对象。
在字典中的一对键和值是通过使用冒号指定的,如,`d = {key1 : value1, key2 : value2 }`。注意,键值对用冒号分隔,彼此之间以逗号分隔,所有这些都是包含在一对大括号中。
记住,在字典中键-值对不以任何方式排序。如果你想要一个特定的顺序,那么你将不得不在使用前自己排序。
你将要使用的字典是`dict`类的对象或实例。
例子 (保存为 `using_dict.py`):
~~~python
# 'ab'是英文address book(地址簿)的首个字母
ab = { 'Swaroop' : 'swaroop@swaroopch.com',
'Larry' : 'larry@wall.org',
'Matsumoto' : 'matz@ruby-lang.org',
'Spammer' : 'spammer@hotmail.com'
}
print("Swaroop的地址是", ab['Swaroop'])
# 删除一个键-值对
del ab['Spammer']
print('\n地址薄中有 {0} 个联系人\n'.format(len(ab)))
for name, address in ab.items():
print('联系人 {0} 的地址是 {1}'.format(name, address))
# 添加一个键-值对
ab['Guido'] = 'guido@python.org'
if 'Guido' in ab:
print("\nGuido的地址是", ab['Guido'])
~~~
输出:
~~~
$ python3 using_dict.py
Swaroop的地址是 swaroop@swaroopch.com
地址薄中有 3 个联系人
联系人 Larry 的地址是 larry@wall.org
联系人 Matsumoto 的地址是 matz@ruby-lang.org
联系人 Swaroop 的地址是 swaroop@swaroopch.com
Guido的地址是 guido@python.org
~~~
它是如何工作的:
我们使用已经讨论过的符号创建字典`ab`。然后我们通过使用在列表和元组中讨论过的索引操作符--指定关键字来访问键-值对,遵守简单的语法。
我们可以使用我们的老朋友——`del`语句删除键值对,我们简单地指定字典和要删除的关键字的索引操作符,并将它传递给`del`语句。对于这个操作,没有必要知道对应于关键字的值。
接下来,我们我们使用字典的`items`方法,访问字典的每个键-值对的。它返回一个元组的列表,每个元组包含一对值--关键字及紧随其后的值。我们检索这对值并使用`for..in`循环为每一对分配给相应的变量`name`和`address`,然后在for块中打印这些值。
我们可以通过简单地使用索引操作符来访问一个键并分配值的方式添加新的键值对,像上面的例子中我们所做的添加Guido。
我们可以使用`in`操作符来检查一个键值对是否存在。
字典`dict`类的列表方法,请看`help(dict)`。
关键字参数和字典
: 有一点不同需要注意,如果你在您已经在使用字典的函数中使用关键字参数,只是这样想,这个键值对是在函数定义的参数列表中指定的,而当你在函数中访问变量,它只是访问字典的一个键(在编译器设计术语中称为*符号表*)。
## 序列
列表、元组和字符串都序列的一个实例,但是什么是序列,它们为什么如此特殊呢 ?
主要特点是**成员测试**,(即`in`(在)和`not in`(不在)表达式中)和**索引操作**,这使我们在一个序列中能够直接获取一个特定的对象。
上面提到的——列表、元组和字符串这三种类型的序列,也有允许我们找回一彼序列即序列的一部分的**切片**操作。
例子 (保存为`seq.py`):
~~~python
shoplist = ['苹果', '芒果', '胡萝卜', '香蕉']
name = 'swaroop'
# Indexing or 'Subscription' operation
print('第0项是', shoplist[0])
print('第1项是', shoplist[1])
print('第2项是', shoplist[2])
print('第3项是', shoplist[3])
print('第-1项是', shoplist[-1])
print('第-2项是', shoplist[-2])
print('第0个字符是', name[0])
# 一个列表的切片
print('第1项到第3项是', shoplist[1:3])
print('第2项到末尾是', shoplist[2:])
print('第1到-1项是', shoplist[1:-1])
print('开头到结尾是', shoplist[:])
# 字符串的切片
print('第1到第3个字符是', name[1:3])
print('第2到末尾的字符是', name[2:])
print('第1到-1的字符是', name[1:-1])
print('从头到尾的字符是', name[:])
~~~
输出:
~~~
$ python3 seq.py
第0项是 苹果
第1项是 芒果
第2项是 胡萝卜
第3项是 香蕉
第-1项是 香蕉
第-2项是 胡萝卜
第0个字符是 s
第1项到第3项是 ['芒果', '胡萝卜']
第2项到末尾是 ['胡萝卜', '香蕉']
第1到-1项是 ['芒果', '胡萝卜']
开头到结尾是 ['苹果', '芒果', '胡萝卜', '香蕉']
第1到第3个字符是 wa
第2到末尾的字符是 aroop
第1到-1的字符是 waroo
从头到尾的字符是 swaroop
~~~
它是如何工作的:
首先,我们看看如何使用索引来获得一个序列的个别项,这也称为*订阅操作*。当你在方括号中指定一个数字对应一个序列中的某项,如上所示,Python会为你取得序列中相对应位置的项。记住,Python从0开始数数。因此,在序列`shoplist`中, `shoplist[0]`取第一项和`shoplist[3]`获取第四项。
索引也可以是负数,在这种情况下,这个位置从序列的结尾开始计算。因此, `shoplist[-1]`指的是序列的最后一项, `shoplist[-2]`取倒数第二个项。
这个切片操作是通过指定序列的名称后面加上一个方括号,方括号中有一对可选的用冒号分隔的数。注意,这非常类似于你到现在一直使用的索引操作,记住这些数字是可选的但冒号不是。
在切片操作中的第一个数字(在冒号前)是切片开始的位置,第二个数字(在冒号后)是切片停止的位置。如果第一个数字没有指定,Python会从序列开头开始,如果第二个数字被冷落,Python会在序列的末尾停止。注意,返回的切片在开始位置*开始*,在*结束*位置前结束,也就是说,返回的切片包含开始位置,但不包含结束位置。
因此, `shoplist[1:3]` 返回序列的切片从位置1开始,包括位置2,但是在位置3停止,因此,返回两个项目的*切片*。同样,`shoplist[:]`返回整个序列的一个副本。
你也可以使用负位置做切片。负数用于从序列的结尾开始。例如,shoplist[:-1]` 将返回一个不包括序列最后一项,但包含了其它一切的切片。
你也可以为切片提供第三个参数,这是切片的*步长*(默认情况下,步长为1):
~~~
>>> shoplist = ['苹果', '芒果', '胡萝卜', '香蕉']
>>> shoplist[::1]
['苹果', '芒果', '胡萝卜', '香蕉']
>>> shoplist[::2]
['苹果', '胡萝卜']
>>> shoplist[::3]
['苹果', '香蕉']
>>> shoplist[::-1]
['香蕉', '胡萝卜', '芒果', '苹果']
~~~
注意,当步长是2时,我们获得位置0、2、……的项目,当步长是3晨,我们获得位置是0、3、等等的项目。
使用Python解释器的交互式提示,尝试指定切片的不同组合,以便你可以立刻看到结果。序列的一大好处是,你可以以同样的方式访问元组、列表和字符串!
## 集合
集合是简单对象的*无序*集合,用于一个集合中对象的存在比它的顺序或发生多少次更重要的时候。
使用集合,你可以测试成员,它是否是集合的子集以及找到两个集合的交集,等等。
~~~
>>> bri = set(['巴西', '俄罗斯', '印度'])
>>> '印度' in bri
True
>>> 'usa' in bri
False
>>> bric = bri.copy()
>>> bric.add('中国')
>>> bric.issuperset(bri)
True
>>> bri.remove('俄罗斯')
>>> bri & bric # 或者 bri.intersection(bric)
{'巴西', '印度'}
~~~
它是如何工作的:
这个例子是非常一目了然的,因为它涉及到学校教的数学的基本集合理论。
## 关联
当你创建一个对象,并赋给它一个值,该变量只是*指向*对象,并不代表对象本身!也就是说,变量名称指向你电脑中内存中的存储对象的那部分。这就是所谓的把名字**绑定**给对象。
一般来说,你不需要担心这个,但是对引用有一个需要你注意的微妙的影响::
Example (save as `reference.py`):
~~~python
print('简单的分配')
shoplist = ['苹果', '芒果', '胡萝卜', '香蕉']
mylist = shoplist # mylist是指向同一对象的另一个名字!
del shoplist[0] # 我买到了第一项物品,因此我从清单中移除它
print('shoplist是', shoplist)
print('mylist是', mylist)
# 注意shoplist和mylist都打印没有‘苹果’的相同的清单
# 证明它们指向相同的对象
print('通过制作完整的切片复制')
mylist = shoplist[:] # 通过制作完整的切片复制
del mylist[0] # 移除第一项
print('shoplist是', shoplist)
print('mylist是', mylist)
# 注意,现在两个清单不同
~~~
输出:
~~~
$ python3 reference.py
简单的分配
shoplist是 ['芒果', '胡萝卜', '香蕉']
mylist是 ['芒果', '胡萝卜', '香蕉']
通过制作完整的切片复制
shoplist是 ['芒果', '胡萝卜', '香蕉']
mylist是 ['胡萝卜', '香蕉']
~~~
它是如何工作的:
在注释中有更多有用的解释。
记住,如果你想要复制一个列表或这种类型的序列或复杂的对象(而不是简单的*对象*如整数),那么您必须使用切片操作复制。如果你只是用另一个变量名指定,两个变量将“关联”到相同的对象,如果你不小心,这可能会引起麻烦。
Per程序员需要注意
: 记住,列表的一个赋值语句并**不**创建一个副本。你必须使用切片操作复制序列。
## 关于字符串的更多
之前,我们已经详细讨论了字符串。在这能了解更多吗?嗯,你知道吗,字符串也是对象和也有做任何事情的方法--从检查的部分字符串到从字符串中分离。
在程序中你使用的字符串都是`str`类的对象,在下面的例子中将演示这个类的一些有用的方法,这些方法的完整列表,请看`help(str)`。
例子 (保存为 `str_methods.py`):
~~~python
name = 'Swaroop' # 这是一个字符串对象
if name.startswith('Swa'):
print('是的,字符串以"Swa"开始')
if 'a' in name:
print('是的,它包含字符串"a"')
if name.find('war') != -1:
print('是的,它包含字符串"war"')
delimiter = '_*_'
mylist = ['巴西', '俄罗斯', '印度', '中国']
print(delimiter.join(mylist))
~~~
Output:
~~~
$ python3 str_methods.py
是的,字符串以"Swa"开始
是的,它包含字符串"a"
是的,它包含字符串"war"
巴西_*_俄罗斯_*_印度_*_中国
~~~
它是如何工作的:
在这里,我们看到字符串的很多方法在起作用。`startswith`方法是用来找出字符串是否以给定的字符串开始的。`in`操作符是用来检查一个给定的字符串是否是一个字符串的一部分。
`find`方法用于定位给定的子字符串在字符串内的位置,如果不能成功找到子字符串它返回-1。`str`类也有一个整洁的方法来连接一个序列的字符串,用充当分隔符的字符串连接序列中每个条目,返回一个由它生成的巨大的字符串。
## 小结
我们详细探索了Python各种内建的数据结构,写合理大小的程序,这些数据结构是至关重要的。
现在,我们有很多Python的基本知识已经到位,下面,我们看看如何设计和写一个真实的Python程序。