Python-数据结构-列表、元组、集合、字典

1 列表详解

列表数据类型支持很多方法,列表对象的所有方法所示如下:

  • list.append(x)

    在列表末尾添加一个元素,相当于 a[len(a):] = [x]

  • list.extend(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() 删除并返回列表的最后一个元素。(方法签名中 i 两边的方括号表示该参数是可选的,不是要求输入方括号。这种表示法常见于 Python 参考库)。

  • list.clear()

    删除列表里的所有元素,相当于 del a[:]

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

    返回列表中第一个值为 x 的元素的零基索引。未找到指定元素时,触发 ValueError异常。可选参数 startend 是切片符号,用于将搜索限制为列表的特定子序列。返回的索引是相对于整个序列的开始计算的,而不是 start 参数。

  • list.count(x)

    返回列表中元素 x 出现的次数。

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

    就地排序列表中的元素。

  • list.reverse()

    反转列表中的元素。

  • list.copy()

    返回列表的浅拷贝。相当于 a[:]

列表方法示例:

fruits = ['orange', 'apple', 'pear', 'banana', 'kiwi', 'apple', 'banana']
a = fruits.count('apple')
print(a)
# 2
b = fruits.count('grape')
print(b)
# 0
c = fruits.index('banana')
print(c)
# 3
d = fruits.index('banana', 4)
print(d)
# 6
e = fruits.index('apple', 2, 6)
print(e)
# 5
fruits.reverse()
print(fruits)
# ['banana', 'apple', 'kiwi', 'banana', 'pear', 'apple', 'orange']
fruits.append('grape')
print(fruits)
# ['banana', 'apple', 'kiwi', 'banana', 'pear', 'apple', 'orange', 'grape']
fruits.sort()
print(fruits)
# ['apple', 'apple', 'banana', 'banana', 'grape', 'kiwi', 'orange', 'pear']
fruits.pop()
print(fruits)
# ['apple', 'apple', 'banana', 'banana', 'grape', 'kiwi', 'orange']
fruits2 = fruits.copy()
fruits.clear()
print(fruits)
# []
print(fruits2)
# ['apple', 'apple', 'banana', 'banana', 'grape', 'kiwi', 'orange']

insertremovesortappend方法只修改列表,不输出返回值——返回的默认值为 None 。这是所有 Python 可变数据结构的设计原则。

还有,不是所有数据都可以排序或比较。例如,[None, 'hello', 10] 就不可排序,因为整数不能与字符串对比,而 None 不能与其他类型对比。有些类型根本就没有定义顺序关系,例如,3+4j < 5+7j 这种对比操作就是无效的。

1.1 用列表返回堆栈

堆栈的最大特点就是“后进先出”。把元素添加到顶端,使用 append() ,从堆栈的顶端去除元素,使用 pop() ,不用指定索引。

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

1.2 用列表实现队列

队列的最大特点是“先进先出”,然而使用列表作为队列的效率很低,因为,在列表末尾添加和删除元素非常快,但在列表开头插入或一出元素缺很慢(其他元素需要移动一位)。

collections 模块提供了deque 对象。deque是为了高效实现插入和删除操作的双向列表,可以快速从两端添加或删除元素。

deque除了实现list的append()pop()外,还支持appendleft()popleft(),这样就可以非常高效地往头部添加或删除元素。

from collections import deque
q = deque(['a', 'b', 'c'])
q.append('x')
q.appendleft('w')
print(q)
# deque(['w', 'a', 'b', 'c', 'x'])

1.3 列表推导式

列表推导式创建列表的方式更简洁。常见的用法为,对序列或可迭代对象中的每个元素应用某种操作,用生成的结果创建新的列表;或用满足特定条件的元素创建子序列。

例如,创建平方值列表:

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

print(squares)
# [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
print(x)
# 9

注意,这段代码会创建(或覆盖)变量 x,该变量在循环结束后仍然存在。下述方法可以无副作用地计算平方列表:

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

高阶函数map()接收两个参数,第一个参数是一个函数,第二个参数是一个Iterable(可迭代对象),map()把传入的函数作用到可迭代对象的每一个元素上并且把结果作为一个新的可迭代对象返回。

或等价于:

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

这种写法更简洁易读。

列表推导式的方括号内包含以下内容:一个表达式,后面为一个 for 子句,然后,是零个或多个 forif 子句。结果是由表达式依据 forif 子句求值计算而得出一个新列表。 举例来说,以下列表推导式将两个列表中不相等的元素组合起来:

a = [(x, y) for x in [1, 2, 3] for y in [3, 1, 4] if X != y]
print(a)
# [(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))

注意,上面两段代码中,forif 的顺序相同。

表达式是元组(例如上例的 (x, y))时,必须加上括号:

>>> vec = [-4, -2, 0, 2, 4]
>>> # 创建一个新列表每个元素的值都是之前的2倍
>>> [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']
>>> # 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)]
>>> # 元组必须加圆括号,否则会出现错误
>>> [x, x**2 for x in range(6)]
  File "<stdin>", line 1, in <module>
    [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
a = [str(round(pi, i)) for i in range(1, 6)]
print(a)
# ['3.1', '3.14', '3.142', '3.1416', '3.14159']

1.4 嵌套的列表推导式

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

下面这个 3x4 矩阵,由 3 个长度为 4 的列表组成:

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

下面的列表推导式可以转置行列:

mat = [[row[i] for row in matrix] for i in range(4)]
print(mat)
# [[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 = []
for i in range(4):
    transposed_row = []
    for row in matrix:
        transposed_row.append(row[i])
    transposed.append(transposed_row)

2 元组和序列

列表和字符串有很多共性,例如索引和切片操作。这两种数据类型是 序列 。除了这两种之外还有另一种标准序列类型,它就是元组

元组由多个用逗号隔开的值组成,例如:

>>> 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])
>>> v[0]
[1, 2, 3]
>>> v[0][1]
2
>>> v[0][1] = 10
>>> v
([1, 10, 3], [4, 5, 6])

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

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

>>> empty = ()
>>> singleton = 'hello',    # <-- 注意后面的逗号
>>> len(empty)
0
>>> len(singleton)
1
>>> singleton
('hello',)

语句 t = 12345, 54321, 'hello!'元组打包 的例子:值 12345, 54321'hello!' 一起被打包进元组。逆操作也可以:

>>> x, y, z = t
>>> x
12345
>>> y
54321
>>> z
'hello!'

称之为 序列解包 也是妥妥的,适用于右侧的任何序列。序列解包时,左侧变量与右侧序列元素的数量应相等。注意,多重赋值其实只是元组打包和序列解包的组合。

3 集合

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

创建集合用花括号或 set()函数。注意,创建空集合只能用 set(),不能用 {}{} 创建的是空字典。

简单示例:

>>> basket = {'apple', 'orange', 'apple', 'pear', 'orange', 'banana'}
>>> print(basket)                      # 重复的元素被去除
{'orange', 'banana', 'pear', 'apple'}
>>> 'orange' in basket                 # 成员检测
True
>>> 'crabgrass' in basket
False

>>> # 展示两个单词中唯一字母的set操作
...
>>> a = set('abracadabra')
>>> b = set('alacazam')
>>> a                                  # 去重后的字母
{'a', 'r', 'b', 'c', 'd'}
>>> a - b                              # 在a里面不在b里面的字母(差集)
{'r', 'd', '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'}

4 字典

字典也是一种常用的Python内置数据类型。与以连续整数为索引的序列不同,字典以 关键字 为索引,关键字通常是字符串或数字,也可以是其他任意不可变类型。只包含字符串、数字、元组的元组,也可以用作关键字。但如果元组直接或间接地包含了可变对象,就不能用作关键字。列表不能当关键字,因为列表可以用索引、切片、append()extend() 等方法修改。

可以把字典理解为 键值对 的集合,但字典的键必须是唯一的。花括号 {} 用于创建空字典。另一种初始化字典的方式是,在花括号里输入逗号分隔的键值对,这也是字典的输出方式。

字典的主要用途是通过关键字存储、提取值。用 del 可以删除键值对。用已存在的关键字存储值,与该关键字关联的旧值会被取代。通过不存在的键提取值,则会报错。

对字典执行 list(d) 操作,返回该字典中所有键的列表,按插入次序排列(如需排序,请使用 sorted(d))。检查字典里是否存在某个键,使用关键字 in

以下是一些字典的简单示例:

>>> num = {'one': 1, 'two': 2, 'three': 3, 'four': 4, 'five': 5}
>>> num
{'one': 1, 'two': 2, 'three': 3, 'four': 4, 'five': 5}
>>> num['six'] = 6
>>> num
{'one': 1, 'two': 2, 'three': 3, 'four': 4, 'five': 5, 'six': 6}
>>> num['four']
4
>>> del num['two']
>>> num
{'one': 1, 'three': 3, 'four': 4, 'five': 5, 'six': 6}
>>> list(num)
['one', 'three', 'four', 'five', 'six']
>>> sorted(num)
['five', 'four', 'one', 'six', 'three']
>>> 'one' in num
True
>>> 'nine' in num
False

dict() 构造函数可以直接用键值对序列常见字典:

dict([('one', 1), ('two', 2)])

字典推导式可以用任意键值表达式创建字典:

>>> {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 循环的技巧

在字典循环时,用 items()方法可同时去除键和对应的值:

num = {'one': 1, 'two': 2}
for k, v in num.items():
    print(k, v)

在序列中循环时,用 enumerate() 函数可以同时取出位置索引和对应值:

for i, v in enumerate(list1):
  print(i, v)

同时循环两个或多个序列时,用 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))

逆向循环序列时,先正向定位序列,然后调用 reversed()函数:

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

按指定顺序循环序列,可以用 sorted()函数,在不改动原序列的基础上,返回一个重新的序列:

basket = ['apple', 'orange', 'apple', 'pear', 'orange', 'banana']
for f in sorted(set(basket)):
    print(f)

一般来说,在循环中修改列表的内容时,创建新列表比较简单,且安全:

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)
posted @ 2021-09-10 11:32  雨-铃  阅读(94)  评论(0编辑  收藏  举报
Tips
复制成功!