Python入门笔记(六) 数据结构

第六篇学习笔记,内容包含Python中常用的数据结构:元组(tuple),列表(list),字典(dictionary),集合(set)四种。

元组

元组是一种非常简单的结构,它类似于数学上的序对,属于不可变类型。它的优点在于运行效率比列表要高。

定义

定义一个元组只需要在圆括号中添加元素,并使用逗号隔开即可(圆括号可以省略)。如:

1
2
3
4
5
6
>>> a = (1, 2, 3)
>>> type(a)
<class 'tuple'>
>>> tp = 12, 3, 4
>>> tp
(12, 3, 4)

空元组即为()
元组中只包含一个元素时,需要在元素后面添加逗号,否则括号会被当作运算符使用。如:

1
2
3
4
5
6
7
8
9
>>> b = (50)
>>> type(b)
<class 'int'>
>>> b = (50, )
>>> type(b)
<class 'tuple'>
>>> tp = 12,
>>> tp
(12,)

生成一个元组

使用tuple()函数将一个列表或者一个字符串(一个可迭代结构也可以)转换成为一个元组。当参数是列表的时候,元组中包含的就是列表中的元素。参数是字符串的话字符串的每一位就会被拆开,放到列表中的每一位中去。

访问元组中的元素

访问方式和字符串基本相同,可以使用下标也可以使用切片(切片得到的是元组)。如:

1
2
3
4
5
>>> tp1 = (1, 2, 3, 4)
>>> tp1[0]
1
>>> tp1[2: 4]
(3, 4)

元组的运算

元组的+*运算方法和字符串相似,前者用于前后连接,后者用于重复。在此不赘述。
使用in运算符可以获取一个元素是否在元组中。

元组的修改

元组不允许修改,因此只能通过运算生成新元组或者使用del将整个元组删除。试图修改元组的元素会报错,如:

1
2
3
4
5
6
7
8
>>> tp1[0] = 1
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
>>> del tp1[0]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object doesn't support item deletion

元组在赋值中的应用

事实上,同时赋值的基础就是元组。可以将同时赋值时右边的式子看作是一个元组,这样就可以理解为左边的被赋值对象和右边元组中的元素一一对应。提供以下例子用于理解:

1
2
3
4
>>> tp = 1, 2, 3
>>> a, b, c = tp
>>> print(a, b, c)
1 2 3

但是右边不能有多个元组,如:

1
2
3
4
5
6
>>> tp = 1, 2, 3
>>> tp1 = 4, 5, 6
>>> a, b, c, d, e, f = tp, tp1
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: not enough values to unpack (expected 6, got 2)

这种方法称为packing(把元素打包)和unpacking(把元素解包)。

列表

列表是一种类似于其他编程语言中数组的存在,但它的功能比数组更为强大。它的数据项不需要具有相同的数据类型。
python中列表是链式储存的,这和数组有很大的不同。

定义

列表的形式是用中括号括起来一些用逗号分隔开来的数据。空列表仅仅包含一对中括号。如:

1
2
list1 = [1, 2, 3]
list_empty = []

生成一个列表

可以使用list()函数将一个元组或者一个字符串(一个可迭代结构也可以)转换成为一个列表。当参数是元组的时候,列表中包含的就是元组中的元素。参数是字符串的话字符串的每一位就会被拆开,放到列表中的每一位中去。
生成一个列表还可以使用列表推导式(在后续笔记中提及)。

列表的嵌套

列表可以层层嵌套,从而实现类似于多维数组(或者矩阵)的功能。如:

1
a = [[1, 2], [3, 4], [5, 6]]  # (3*2)矩阵

访问列表中的元素

访问方式和字符串基本相同,可以使用下标也可以使用切片(切片得到的是列表)。如:

1
2
3
4
5
>>> list1 = [1, 2, 3, 4]
>>> list1[0]
1
>>> list1[2: 4]
[3, 4]

添加列表元素

可以用列表的append(val)方法在列表的末尾添加一个元素val
也可以用列表的insert(index, val)方法在下标为index处添加一个元素val,然后将原来index处的元素以及其后面的元素都向后移动一位。
使用extend(list)方法可以在列表的末尾一次性按顺序添加上一个另一个列表的全部元素。

删除列表元素

使用del语句可以删除列表中若干个元素,如:

1
2
3
4
>>> list1 = [1, 2, 3, ]
>>> del list1[0], list1[1]
>>> list1
[2]

可以使用列表的pop(index = -1)方法来删除列表的下标为index的元素,并且将这一个元素返回。index参数缺省时为-1,即为最后一个元素的下标。如:

1
2
3
4
5
>>> list1 = [1, 2, 3, ]
>>> print(list1.pop())
3
>>> list1
[1, 2]

也可以用remove(val)方法删除从前到后,列表中第一个值为val的元素。如果没有这样的元素就会报错。如:

1
2
3
4
5
6
7
8
>>> list1 = [1, 2, 3, 4, 3, ]
>>> list1.remove(3)
>>> list1
[1, 2, 4, 3]
>>> list1.remove(5)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: list1.remove(x): x not in list1

列表的运算

列表的+*运算方法和字符串相似,前者用于前后连接,后者用于重复。在此不赘述。
使用in运算符可以获取一个元素是否在列表中。

组织列表

对列表排序

使用sort(reverse = False)方法对列表进行永久性的排序,这个方法会改变列表中元素的顺序。其中缺少reverse参数时默认为升序排序,加上reverse = True时会降序排序。如:

1
2
3
4
5
6
7
>>> list1 = [1, 2, 3, 4, 3, ]
>>> list1.sort()
>>> list1
[1, 2, 3, 3, 4]
>>> list1.sort(reverse = True)
>>> list1
[4, 3, 3, 2, 1]

使用sorted(list, reverse = False)函数可以将list进行临时的排序,这不会改变list内部数据的排列顺序。如:

1
2
3
4
5
6
7
>>> list1 = [1, 2, 3, 4, 3, ]
>>> sorted(list1)
[1, 2, 3, 3, 4]
>>> sorted(list1, reverse = True)
[4, 3, 3, 2, 1]
>>> list1
[1, 2, 3, 4, 3]

翻转列表

使用reverse()方法永久性翻转列表。如:

1
2
3
4
>>> list1 = [1, 2, 3, 4, 3, ]
>>> list1.reverse()
>>> list1
[3, 4, 3, 2, 1]

列表的其他实用方法

查找

使用count(val)方法可以查找列表中值为val的元素个数。不存在这样的元素的时候会简单地返回0。如:

1
2
3
>>> list1 = [3, 4, 3, 2, 1]
>>> list1.count(0)
0

使用index(val)方法可以查找从前到后,列表中第一个值为val的元素,并返回它的下标。如果没有这样的元素就会报错。如:

1
2
3
4
5
6
7
>>> list1 = [3, 4, 3, 2, 1]
>>> list1.index(0)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: 0 is not in list1
>>> list1.index(3)
0

清除

clear()方法清除列表中的所有元素。

复制

用切片操作或者copy()方法可以获得一份当前列表的副本。

字典

字典保存的是对象间的映射关系,在python中用大括号表示。它是另一种可变容器模型,且可存储任意类型对象。
字典和列表、元组不同,没有+*运算符。字典在python3.6版本之后默认保持插入时的顺序。

创建一个字典

字典中键->值的映射是通过冒号来表示的,按如下方式即可创建一个字典:

1
dict1 = {1: 2, 2: 3}

其中键必须是唯一不可变的。但值没有这样的限制。
使用dict()函数也可以创建一个字典。该函数的语法比较复杂,以下举一些例子:

  1. 不传入参数,返回一个空字典。如dict1 = dict(),得到{}
  2. 传入的参数是一个字典,则返回该字典。如dict1 = dict({'a': 1, 'b': 2}),得到{'a': 1, 'b': 2}
  3. 传入的参数是由二元组构成的列表,则返回由这些二元组构成的字典。若键发生重复则以后面出现的键值对为准。如dict1 = dict([(12, 25), (13, 14), (12, 28)]),得到{12: 28, 13: 14}
  4. 按关键字形式传入参数,则返回由“关键字-参数”键值对构成的字典。如dict1 = dict(a = 1, b = 2),得到{'a': 1, 'b': 2}
  5. 传入的参数是zip类型的对象,则和3的情况类似。如dict1 = dict(zip([1, 2, 3, 2], [4, 5, 6, 7])),得到{1: 4, 2: 7, 3: 6}
  6. 传入的参数是dict.items类型,则返回由该参数包含的键值对构成的字典(相当于将此字典还原)。如dict1 = dict({1: 2}.items()),得到{1: 2}

也可以使用字典推导式创建字典(在后续笔记中提及)。
也可以使用一些字典的方法实现字典的创建,具体见下。

访问一个字典

将键作为索引可以得到键对应的值。如果没有这样的键就会报错
可以使用get()方法防止报错。该方法语法是dict.get(key, default = None),可以获取key对应的值,如果这个值不存在就返回default
使用key in dict可以判断一个键是否在字典中,但不能判断值是否在字典中。使用for key in dict也是按照键对字典进行遍历。

修改一个字典

修改键对应的值

将键作为索引并赋值可以改变键对应的值。如果没有这样的键就会在字典中增加一个这样的键-值对。如:

1
2
3
dict1 = {}  # 空字典
dict1['a'] = 1 # 创建一个新键-值对
dict1['a'] = 2 # 修改键对应的值

删除键-值对

使用del可以删除键-值对。如:

1
2
dict1 = {1: 2, 2: 3}
del dict1[1] # 删除这个键对应的键-值对

如果要删除的键不存在会报出KeyError异常。
可以使用pop()方法防止报错。该方法语法是pop(key[, default])key为要删除的键,如果key存在就返回被删除的值,不存在就返回default(此时如果省略default会报出KeyError异常)。
使用popitem()方法可以删除字典中的某一个键值对。至于是哪一个就只有python知道。如果此时字典为空,则报错。

字典相关方法

求字典的大小

使用len()函数可以求出字典中键的数目。

求字典的键/值/键-值对

方法keys()values()items()分别返回dict_keysdict_valuesdict_items类型的迭代器,它们分别含有字典的所有键,值和键-值对。可以使用list()方法将这些迭代器转换成为列表使用。
可以使用for语句遍历这三种结构,如:

1
2
3
4
for k in dict1.values():
pass
for k, v in dict1.items():
pass

打印字典

将字典作为str()函数的参数即可按照特定格式将字典输出。

清空字典

使用clear()方法清空字典。

复制字典

copy()方法可以产生一个字典的副本。

创建字典

使用fromkeys()方法可以创建一个新的字典。它的语法是dict.fromkeys(seq[, value])fromkeys()是类方法),其中seq是键的列表(该列表中可以包含相同元素),value为新字典中所有键对应的初始值,缺省时为None。该方法返回一个字典。
值得一提的是,在该方法中,初始值被“复制”了。而做实验可以发现该复制是浅复制。如下程序:

1
2
3
4
>>> dict1 = dict.fromkeys([1, 2], [2, 3])
>>> dict1[1][0] = 0
>>> dict1
{1: [0, 3], 2: [0, 3]} # 列表全部被改变了!!!

合并字典

字典合并有多种方法。以下均以dict1和dict2合并为dict3来举例。
方法一是使dict3 = dict(list(dict1.items()) + list(dict2.items()))。由此可看出,当dict1和dict2中有相同的键时,后者会覆盖前者。如:

1
2
3
4
5
>>> dict1 = {1: 2, 2: 4}
>>> dict2 = {1: 3}
>>> dict3 = dict(list(dict1.items()) + list(dict2.items()))
>>> dict3
{1: 3, 2: 4}

方法二是使dict3 = dict(dict1, **dict2)。此时dict2中的键值对会覆盖dict1的。该方法容易扩展到多个字典合并到一个字典的情况,即dictn = dict(dict1, **dict2, **dict3, ...)。对于多合并的情况,要注意后面加了两个星号的字典两两不能有重复的键,且键必须为字符串。如:

1
2
3
4
5
>>> dict1 = {'a': 1, 'b': 2}
>>> dict2 = {'c': 3, 'd': 4}
>>> dict3 = dict(dict1, **dict2)
>>> dict3
{'a': 1, 'b': 2, 'c': 3, 'd': 4}

方法三是遍历字典并把键值对放入新字典中。该方法比较直观,不赘述。
方法四是使用字典的update()方法。该方法将一个字典的键值对更新到调用方法的字典中,如有重复则覆盖。如:

1
2
3
4
5
>>> dict1 = {1: 2, 3: 4}
>>> dict2 = {2: 5, 1: 6}
>>> dict1.update(dict2)
>>> dict1
{1: 6, 3: 4, 2: 5}

集合

集合在数学上是一个包含无序的不重复元素的结构。在Python中也是如此。

定义

集合的形式是用大括号括起来一些用逗号分隔开来的数据。如:

1
2
3
>>> a = set((1, 2, 3, 4, 5, 5, 6))
>>> a
{1, 2, 3, 4, 5, 6}

需要注意的是,空集合并不是一对大括号,而是这样的形式:

1
2
3
>>> a = set()
>>> a
set()

生成一个集合

可以使用set()函数将一个可迭代结构/序列转换成为一个集合。参数是字符串的话字符串的每一位就会被拆开,去重后放入集合。
生成一个集合还可以使用集合推导式(在后续笔记中提及)。

添加集合元素

使用add()方法向集合内添加一个元素。如果该元素已经存在则不进行操作。如:

1
2
3
4
5
6
>>> a = set((1, 2, 3, 4))
>>> a
{1, 2, 3, 4}
>>> a.add(1)
>>> a
{1, 2, 3, 4}

也可以使用update()方法一次性添加多个元素,只需要传入一个可迭代结构即可。如:

1
2
3
4
>>> a = {1, 2, 3, 4}
>>> a.update([5, 6, 7])
>>> a
{1, 2, 3, 4, 5, 6, 7}

删除集合元素

使用discard()remove()方法移除某个指定的元素。如果该元素不存在,前者不会报错,后者会。如:

1
2
3
4
5
6
7
8
9
>>> C = {1, 2}
>>> C.discard(1)
>>> C
{2}
>>> C.discard(1)
>>> C.remove(1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 1

集合相关操作

基本运算

设A, B为两个set。则A & BA | BA ^ BA - B分别返回A和B的交,并,对称差和差,分别对应的方法为intersection()union()
symmetric_difference()difference()。前两个方法可以传入多个集合作为参数,表示多个集合参与交/并运算。如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
>>> A = {1, 2, 3, 4}
>>> B = {2, 3, 4, 5}
>>> C = {3, 4, 5, 6}
>>> A & B
{2, 3, 4}
>>> A | B
{1, 2, 3, 4, 5}
>>> A ^ B
{1, 5}
>>> A - B
{1}
>>> A & B & C
{3, 4}
>>> A.intersection(B, C)
{3, 4}
>>> A.union(B, C)
{1, 2, 3, 4, 5, 6}

如果在交/对称差/差运算对应的方法名后加入_update,则表示进行运算后把结果赋给调用该方法的集合。如:

1
2
3
4
5
6
7
8
9
>>> A = {1, 2, 3, 4}
>>> B = {2, 3, 4, 5}
>>> C = {3, 4, 5, 6}
>>> A.intersection_update(B, C)
>>> A
{3, 4}
>>> B.symmetric_difference_update(C)
>>> B
{2, 6}

从属关系

in可以检查一个元素是否在一个集合中。

包含关系

使用issubset()方法判断一个集合是否是另一个的子集,使用issuperset()方法判断一个集合是否是另一个的超集。如:

1
2
3
4
5
6
>>> A = {1, 2, 3, 4}
>>> B = {1, 2, 3}
>>> B.issubset(A)
True
>>> A.issuperset(B)
True

>=<=也可以判断包含关系。用><可以判断真包含关系。

相交关系

使用isdisjoint()方法判断两个集合是否不相交,返回值为布尔类型。如:

1
2
3
4
5
6
7
>>> A = set()
>>> B = {1}
>>> C = {1, 2}
>>> C.isdisjoint(B)
False
>>> C.isdisjoint(A)
True

复制集合

使用copy()方法产生一个集合的副本。

清空集合

使用clear()方法清空集合中所有的元素。