数据结构
本章会更细节的讲一些你已经学过的东西,同时也会加一些新的内容。
5.1 List有关方法
list有许多方法,以下是list方法的所有实例: list.append(x)
添加1个item到list的末尾,等同于a[len(a):] = [x]
list.extend(iterable)
以迭代器方式追加的所有item拓展list,等同于a[len(a):] = iterable
list.insert(x)
把x插入到list的指定位置。第一个参数是插入的位置,a.insert(0, x)
插入到list头, a.insert(len(a), x)
相当于a.append(x)
。 list.remove(x)
移除list中第一个等于x的item。如果没有这个item会报错 list.pop([i])
移除list中指定位置的item,同时返回item。如果位置没有指定,a.pop()'会移除list的最后1个item,并返回它。中括号中的参数x是可选的,中括号不是必须的。你在Python手册中会经常看见这种用法。
list.clear() 清空list,等同于
del a[:]
list.index(x[, start[, end]]) 返回第一个等于x的item的序列位置,是从0开始算。如果没有等于x的item方法会报
ValueError。 可选参数作为切片使用,定义了起始和结束位置。返回的位置是相对于原先完整的list而不是起始参数。
list.count(x) 返回list中x出现的次数
list.sort(key=None, reverse=False) 本地对list排序(参数是拿来定义sort,去sorted()看具体的解释吧)
list.reverse() 不需要额外空间,倒排list。
list.copy() 返回list的浅拷贝。相当于
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) # Find next banana starting a position 4 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方法仅修改list本身 仅返回None。这是Python 可变数据结构的设计哲学。
5.1.1 像栈一样使用list
list这些方法使得list非常容易像栈一样被使用,最后1个插入的元素首先被取出,添加到栈顶使用append();获取栈顶元素使用pop(),不需要特别指定次序:
fruits = ['orange', 'apple', 'pear', 'banana', 'kiwi', 'apple', 'banana']
fruits.count('apple')
2
fruits.count('tangerine')
0
fruits.index('banana')
3
fruits.index('banana', 4) # Find next banana starting a position 4
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'
5.1.2 像队列一样使用list
当然也可以把list作为队列,先进先出,但是,作为队列list并不高效。因为从list尾部追加删除原色很快,但是从list头做同样操作就很慢了,因为其他所有元素都要移动1次。 真要实现队列,请使用collections.deque
这个专门的数据结构,它2端插入提取都很快:
from collections import deque
queue = deque(["Eric", "John", "Michael"])
queue.append("Terry") # Terry arrives
queue.append("Graham") # Graham arrives
queue.popleft() # The first to arrive now leaves
'Eric'
queue.popleft() # The second to arrive now leaves
'John'
queue # Remaining queue in order of arrival
deque(['Michael', 'Terry', 'Graham'])
5.1.3 List 列表解析式
list 的列表解析式提供了一种简洁的创建list的方法。在以下场景我们常使用list列表解析式:生成1个新的list,它的元素是有原始序列数据结构中取出每个元素操作;或者产生原始序列结构的子集。 比如说你想创建原始list的平方list:
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。新生成的list会由最外围中括号的for if来计算。举个例子,以下代码混合了2个list中互相不等的元素到新的list。
[(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)]
如果[]中的表达式是tuple,像前面例子 (x,y),它必须是在括号内。
vec = [-4, -2, 0, 2, 4]
# create a new list with the values doubled
[x*2 for x in vec]
[-8, -4, 0, 4, 8]
# filter the list to exclude negative numbers
[x for x in vec if x >= 0]
[0, 2, 4]
# apply a function to all the elements
[abs(x) for x in vec]
[4, 2, 0, 2, 4]
# call a method on each element
freshfruit = [' banana', ' loganberry ', 'passion fruit ']
[weapon.strip() for weapon in freshfruit]
['banana', 'loganberry', 'passion fruit']
# create a list of 2-tuples like (number, square)
[(x, x**2) for x in range(6)]
[(0, 0), (1, 1), (2, 4), (3, 9), (4, 16), (5, 25)]
# the tuple must be parenthesized, otherwise an error is raised
[x, x**2 for x in range(6)]
File "", line 1, in
[x, x**2 for x in range(6)]
^
SyntaxError: invalid syntax
# flatten a list using a listcomp with two '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]
list列表表达式中科院包含更复杂的表达和运算:
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 列表解析式的细节
最初的列表解析式中科院包含任何二元运算符,包括另一个列表解析式。下面这个list是一个3*4的矩阵:
matrix = [
... [1, 2, 3, 4],
... [5, 6, 7, 8],
... [9, 10, 11, 12],
... ]
以下代码将上面的3*4矩阵进行转置:
[[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):
... # the following 3 lines implement the nested listcomp
... 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]]
在真实代码场景中,你更应该使用Python的内置方法进行以上复杂的操作。zip()起相同的作用:
list(zip(*matrix))
[(1, 5, 9), (2, 6, 10), (3, 7, 11), (4, 8, 12)]
看 Unpacking Argument Lists 这一节能得到上面代码的更多细节。
5.2 删除语句
根据次序移除list中的元素。与pop()不同,pop()返回弹出的元素,它不返回。del还可以切片的删除list元素,或者清空整个list,早先我们有个这样的例子。
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 Tuple 和 Sequence
我们注意到list和字符串有许多共同点,比如说索引和切片操作。以下是2个序列数据类型的例子(具体请看Sequence Types — list, tuple, range)。由于Python一直在进化,也许会有更多的序列数据类型加入。下面是另外一种标准库里的序列数据类型:tuple.一个tuple包含了一系列被逗号隔开的数据,比如说:
t = 12345, 54321, 'hello!'
t[0]
12345
t
(12345, 54321, 'hello!')
# Tuples may be nested:
... u = t, (1, 2, 3, 4, 5)
u
((12345, 54321, 'hello!'), (1, 2, 3, 4, 5))
# Tuples are immutable:
... t[0] = 88888
Traceback (most recent call last):
File "", line 1, in
TypeError: 'tuple' object does not support item assignment
# but they can contain mutable objects:
... v = ([1, 2, 3], [3, 2, 1])
v
([1, 2, 3], [3, 2, 1])
输出tuple总是被括号包围,它里面的tuple有木有括号都能被解释正确;即使括号是必须的,它们的输入tuple也不一定有括号包围。不可以对tuple中的单个元素复制,但是,tuple中的元素是可变对象时,可以被改变,比如说list。 即使tuple和list看着蛮像,但它们通常在不同的情景不同的目的下使用。Tuple是不可变的,通常是包含了各种各样的序列元素,这些元素可以通过解包(后面会提到)或者指定位置来触达。List是可变的,元素通常是同一类的,我们可以通过迭代器遍历整个list。 必须提到的是,构造tuple的时候。空tuple由一对括号初始化,1个元素的tuple初始化应当由1个值和逗号组成。看着丑但是有效哦。
empty = ()
singleton = 'hello', # <-- note trailing comma
len(empty)
0
len(singleton)
1
singleton
('hello',)
t = 12345, 54321, 'hello!'
是tuple解包的例子,12345,54321,’hello’都是tuple的元素。反过来的操作如下: x, y, z = t
以上序列解包操作适用于任何等式右边的序列。序列解包操作要求等式左边的变量个数等同于右边序列的原色个数。当然多元赋值也是可以的,只是右边序列解包操作出来的元素混进tuple输出。
5.4 集合(Sets)
Python引入Set这种无序的,无重复元素的数据结构。我们通常用它来消除重复元素和进行隶属测试。Set也支持一些数学操作 ,交,并,差,对称差。 用中括号或set()来新建sets,需要注意的是,新建空set必须使用set()而不是{},后者是新建dict,我们下一章再讨论dict。 下面是set的简要使用:
basket = {'apple', 'orange', 'apple', 'pear', 'orange', 'banana'}
print(basket) # show that duplicates have been removed
{'orange', 'banana', 'pear', 'apple'}
'orange' in basket # fast membership testing
True
'crabgrass' in basket
False
# Demonstrate set operations on unique letters from two words
...
a = set('abracadabra')
b = set('alacazam')
a # unique letters in a
{'a', 'r', 'b', 'c', 'd'}
a - b # letters in a but not in b
{'r', 'd', 'b'}
a | b # letters in a or b or both
{'a', 'c', 'r', 'd', 'b', 'm', 'z', 'l'}
a & b # letters in both a and b
{'a', 'c'}
a ^ b # letters in a or b but not both
{'r', 'd', 'b', 'm', 'z', 'l'}
与list列表解析式类似,set也支持这样操作:
a = {x for x in 'abracadabra' if x not in 'abc'}
a
{'r', 'd'}
5.5 字典(Dictionaries)
另一个很有用的Python内置数据结构是字典。Dictionaries有时在其他计算机语言里,作为对应储存,对应序列。不比被顺序索引的序列数据类型,Dictionaries是不可变的结构,它被key索引,字符串,数字经常被用作key。Tuple也可以被用作key,只要它仅由数字,字符串或者tuple组成,如果该Tuple直接或间接包含可变对象,那它就不能被用作dictionary的key了。也不能把list用作dictionary的key,因为list不需要额外空间就能被指定索引修改,切片或appen(),enxtend()这样的方法。 对dictionary最好的理解是一个无序的键值对,key在这个dictionary是独一无二的。一对中括号可以创建一个空dictionary。用逗号分隔,按照key:value这样的格式,由{}包围,可以初始化一个dictionary,这也是dictionary输出的格式。 dictionary的主要用法是储存键值对以及通过key拿到value。通过del也能删除一个键值对。如果1个key已经使用,再使用它的值会被更新。用一个不存在的键来取值会报错。 list(d.keys())
可以以list的形式随机随机顺序获取dictionary的键,当然可以使用sorted(d.keys()获取顺序键list。想知道一个键是否存在于dictionary,使用in
。 这里有些dictionary使用的小例子:
python
tel = {'jack': 4098, 'sape': 4139}
tel['guido'] = 4127
tel
{'sape': 4139, 'guido': 4127, 'jack': 4098}
tel['jack']
4098
del tel['sape']
tel['irv'] = 4127
tel
{'guido': 4127, 'irv': 4127, 'jack': 4098}
list(tel.keys())
['irv', 'guido', 'jack']
sorted(tel.keys())
['guido', 'irv', 'jack']
'guido' in tel
True
'jack' not in tel
False
dict()
可以从键值对序列构建一个dictionary:
dict([('sape', 4139), ('guido', 4127), ('jack', 4098)])
{'sape': 4139, 'jack': 4098, 'guido': 4127}
dictionary解析式可以用键值对来创建dictionary。
{x: x**2 for x in (2, 4, 6)}
{2: 4, 4: 16, 6: 36}
当键是简单字符串时,经常用关键词等式来创建1个dict。
dict(sape=4139, guido=4127, jack=4098)
{'sape': 4139, 'jack': 4098, 'guido': 4127}
5.6 遍历方法
通过items()
可以同时遍历dictionary的键值对。
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
要同时遍历2个或者更多的序列数据结构,可以通过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.
要反向遍历序列,首先要指定list头,然后把改list作为reversed()
的参数执行。
for i in reversed(range(1, 10, 2)):
... print(i)
...
9
7
5
3
1
要遍历一个序列,使用sorted
对原始list操作会输出一个新的有序list,但是原始list不变。
basket = ['apple', 'orange', 'apple', 'pear', 'orange', 'banana']
for f in sorted(set(basket)):
... print(f)
...
apple
banana
orange
pear
在遍历list的时候有时候想修改它,与其修改不如建立个新的list,这样更简单更方便。
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 条件语句
if和while语句中包含的条件语句,可不仅仅只是比较,可以是任何操作符。 in 和not操作是原来确认值是否存在于序列中。is和is not是确认2个实例对象是否相同;它仅可以作用于可变对象例如说list这样。所有的比较操作符都有相同的运算优先级,都低于数字操作符。 比较操作可以连起来。比如说a<b==c
是测试a<b且b=c。 比较操作符可以和二元操作符一起使用:and, o,比较操作符的结果可以被not颠倒。and, or, not的运算优先级均低于比较操作符,其中,not是有最高优先级,or是最低的,所以,A and not B or C
等同于(A and (not B)) or C。通常括号能展现它们的各自组成。 and, or这俩二元操作符被称为短路操作符,参数由左到右运算,有结果输出。如果A and B是true, 那即使B是flase, A and B and C
也不会去计算C的结果。当二元操作符被用作普通值运算而不是二元运算,短路操作的返回值就是最后一个参数。 可以吧比操作运算和其他二元运算的结果赋值给一个变量
string1, string2, string3 = '', 'Trondheim', 'Hammer Dance'
non_null = string1 or string2 or string3
non_null
Trondheim
不像C语言,Python的复制操作不能在操作符运算中出现。C语言开发者也许会对此困惑,但这条禁令有效的避免了C语言中常常出现的一种错误:在表达式中输入=
而在此场景中需要的是==
。
5.8. 序列结构和其他数据结构类型的比较操作
序列结构实例也许会和另外的相同数据结构的实例进行比较操作。比较操作会按照字母顺序:2个序列的第一个元素比较,如果它们不同,那输出比较操作;如果相同,下一对元素进行比较,直到一方元素遍历结束。如果是相同类型的序列结构,字母级比较操作还会递归进行。如果所有的元素都相同,那我们任务这俩序列结构的实例是相同的。如果一个是另外一个的子集,那短的就是小的那一个。字母级比较使用字符串的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)
比较2个不同数据类型的实例也是可以的,只要比较的对象都有合适的方法。比如说不同的数字类型比较时,比较对象会转换成它们的数值,如0变成0.0。除此之外,解释器都会报TypeError异常。
原创文章,作者:奋斗,如若转载,请注明出处:https://blog.ytso.com/tech/pnotes/62240.html