Python 入门 —— 基本数据结构
基本数据类型
变量值的是存储在内存中的,解释器会根据变量的数据类型来分配内存空间,不同类型的数据具有不同的存储方式。基本数据类型主要包括三类:
- 数字:整数、浮点数、复数
- 文本:字符或字符串
- 逻辑值
Python
中的基本数据类型包括 6
种,使用 type
函数返回变量的类型,使用 isinstance
函数来判断变量的类型,
- 整型(
int
)
a = 1000
type(a)
# int
int(1.0)
# 1
isinstance(int(1.0), int) # 类型转换
# True
- 浮点型(
float
)
a = 2.5
type(a)
# float
float('-Inf') # 负无穷大
# -inf
float('Inf') > 10**10
# True
- 复数(
complex
)
a = 1 + 2j
b = 3 - 4j
c = a * b
# (11+2j)
isinstance(c, complex)
# True
- 逻辑型(
bool
)
0 == 0.0
# True
bool('0')
# True
bool(0)
# False
-
字符串(
str
)Python
中字符串可以用单引号和双引号包裹来创建,两种方式创建的字符串没有任何区别,还可以使用三个单引号或双引号来创建字符串,三重引号的字符串可以跨越多行,其所有的空白字符都将包含在该字符串字面值当中,通常用于文档注释。
a = 'abc'
b = "ABC"
c = """matrix:
1 2 3
4 5 6
7 8 9"""
a == "abc"
# True
a == b
# False
print(c)
# matrix:
# 1 2 3
# 4 5 6
# 7 8 9
字符串中某些字符具有特殊含义的,例如 '\n'
表示换行,Python
中还有一种用 r/R''
开头的原始字符串,所有字符都按字面意思使用,不会转义,但是返回的是默认的转义模式字符串。例如 '\n'
变成了 '\\n'
,在正则表达式中比较常用
r'abd\n\t'
# 'abd\\n\\t'
- 二进制类型(
bytes
)
# 使用构造函数创建,并指定编码方式
bytes('中国,你好', encoding='utf-8')
# b'\xe4\xb8\xad\xe5\x9b\xbd\xef\xbc\x8c\xe4\xbd\xa0\xe5\xa5\xbd'
# 使用字符串创建,只能使用 ASCII 字符
b'bcdha'
# b'bcdha'
空对象(None
)
Python
中的空对象使用 None
表示,是一个 NoneType
对象,对于无返回值的函数会默认返回一个 None
,记住空对象不等于空值
a = None
type(a)
# NoneType
def func():
print("test")
a = func()
type(a)
# NoneType
a == ''
# False
a == None
# True
a is None
# True
序列结构
所谓序列,便是依次按顺序排列的多个对象构成的结构,相邻对象之间是前驱和后继的关系。栈和队列都可以看成是序列数据结构。
Python
中对应的是 list
(列表)和 tuple
(元组)
list
创建列表
Python
列表的创建可以使用中括号,或者使用 list
关键字强制转换,列表是一种可以容纳任意类型数据的可变容器,且不会对数据类型进行强制转换。例如
a = [1, 3, 4, 6]
b = ["a", "b", "c"]
c = list('abcd')
# ['a', 'b', 'c', 'd']
list(range(1, 10)) # 类似于 R 中的 seq,返回的是迭代器,需要使用 list 转换
# [1, 2, 3, 4, 5, 6, 7, 8, 9]
d = [1, 3, "A", "abc", a]
[1] * 4 # 元素重复
# [1, 1, 1, 1]
# 创建空列表
alist = []
blist = list()
列表索引
Python
中所有索引都是从 0 开始的,逆向索引(从后往前)是从 -1
开始
mlist = ['red', 'green', 'blue', 'white', 'black', 'orange', 'pink']
mlist[0]
# 'red'
mlist[-1]
# 'pink'
索引切片,在中括号中使用冒号分隔,形如 [start:stop:step]
,三个值都可以缺省,相当于 slice
函数,但是该函数不支持关键字参数。切片索引是半开区间,即包含 start
索引出的值,但是不包含 stop
索引处的值。
mlist[0:4] # 从 0 开始也可以省略 0,相当于 mlist[:4],表示从第一个元素到 stop 前一个元素
# ['red', 'green', 'blue', 'white']
mlist[slice(0, 4)]
# ['red', 'green', 'blue', 'white']
mlist[1:-2]
# ['green', 'blue', 'white', 'black']
省略 stop
,表示从 start
到最后一个元素。如果 slice
函数值设置一个参数,表示的是 stop
位置的索引
mlist[3:]
# ['white', 'black', 'orange', 'pink']
mlist[slice(3)]
# ['red', 'green', 'blue']
mlist[1::2]
# ['green', 'white', 'orange']
省略 start
和 stop
,表示列表的一份拷贝
mlist[:] # 或者 mlist[::]
# ['red', 'green', 'blue', 'white', 'black', 'orange', 'pink']
mlist[::2]
# ['red', 'blue', 'black', 'pink']
设置步长
# 逆序
mlist[::-1]
# ['pink', 'orange', 'black', 'white', 'blue', 'green', 'red']
mlist[2:6:2]
# ['blue', 'black']
mlist[slice(2, 6, 2)]
# ['blue', 'black']
# 如果 start > stop,返回空列表,需要再设置 step 为负值
mlist[3:1]
# []
mlist[3:1:-1]
# ['white', 'blue']
mlist[-1:-4:-1]
# ['pink', 'orange', 'black']
修改列表
同 R
类似,也可以根据索引来修改值,如果是范围索引,则该范围内的值会被替换为设置的列表。如果设置标量值,则会引发异常
mlist[2] = 'yellow'
mlist
# ['red', 'green', 'yellow', 'white', 'black', 'orange', 'pink']
mlist[2:5] = 'yellow' # 字符串会转换为列表
mlist
# ['red', 'green', 'y', 'e', 'l', 'l', 'o', 'w', 'orange', 'pink']
mlist[2:5] = [0]
mlist
# ['red', 'green', 0, 'l', 'o', 'w', 'orange', 'pink']
添加元素,可以使用 append 往末尾添加,insert 往指定位置添加
mlist = ['red', 'green', 'blue', 'white', 'black', 'orange', 'pink']
# 末尾添加
mlist.append('yellow')
# 首位置添加
mlist.insert(0, 1)
mlist
# [1, 'red', 'green', 'blue', 'white', 'black', 'orange', 'pink', 'yellow']
合并两个列表
list1 = [1, 3, 5]
list2 = ['a', 'b', 'c']
list1 + list2
# [1, 3, 5, 'a', 'b', 'c']
list1.extend(list2)
list1
# [1, 3, 5, 'a', 'b', 'c']
删除元素
del mlist[0]
mlist
# ['red', 'green', 'blue', 'white', 'black', 'orange', 'pink', 'yellow']
del mlist[3:5]
mlist
['red', 'green', 'blue', 'orange', 'pink', 'yellow']
列表方法
- 自带方法
函数 | 描述 |
---|---|
append(object) | 在列表的末尾添加元素 |
clear() | 情况列表中的元素 |
copy() | 返回列表的浅拷贝 |
count(value) | 统计列表中某一元素出现的次数 |
extend(iterable) | 合并另一个列表 |
index(value, start, stop) | 返回指定元素第一次出现的索引,元素不存在会抛出异常 |
insert(index, object) | 在指定位置插入元素 |
pop(index=-1) | 删除并返回指定索引处的元素,如果列表为空或索引超出范围会抛出异常 |
remove(value) | 删除首次出现的指定值,值不存在会抛出异常 |
reverse() | 将列表逆序 |
sort(*, key=None, reverse=False) | 排序 |
- 内置函数
函数 | 描述 |
---|---|
len(obj) | 计算列表的长度 |
max(iterable, *[, default=obj, key=func]) | 计算列表中的最大值,必须是同类型的值 |
max(arg1, arg2, *args, *[, key=func]) | |
min(max(iterable, *[, default=obj, key=func])) | 计算列表中的最小值,必须是同类型的值 |
min(arg1, arg2, *args, *[, key=func]) | |
sum(iterable, /, start=0) | 计算列表的和,必须是同类型的值 |
- 成员判断
a = [1, 3, 9, 6, 0, 5]
3 in a
# True
3 not in a
# False
tuple
元组与列表非常相似,唯一的不同之处在于元组是不可变的,任何对元组的修改操作都会引发异常,元组使用小括号创建而不是中括号。
a = (1, 2, 3)
a[1] = 2 # 修改元组会引发异常
# ---------------------------------------------------------------------------
# TypeError Traceback (most recent call last)
# <ipython-input-198-94c4449d0396> in <module>
# ----> 1 a[1] = 2
#
# TypeError: 'tuple' object does not support item assignment
b = ('a', 'e', 'i', 'o', 'u')
b[1:4]
# ('e', 'i', 'o')
# 定义空元组
c = ()
c = tuple()
元组的自带方法只有两个:count
和 index
,功能同 list
。
创建单元素元组,需要在元素后面添加一个逗号 ,
,否则小括号会被当做运算符
a = (1)
type(a)
# int
a = (1, )
type(a)
# tuple
虽然元组是不可变的,但是与列表类似,其可以包含任意类型的元素,如果元组中包含可变的列表,那能不能修改这个元组呢?
student = ('Tom', 22, 'male', ['English', 'Biology'])
student[-1].append('Chinese')
student
# ('Tom', 22, 'male', ['English', 'Biology', 'Chinese'])
del student[-1][0]
student
# ('Tom', 22, 'male', ['Biology', 'Chinese'])
del student[-1]
# ---------------------------------------------------------------------------
# TypeError Traceback (most recent call last)
# <ipython-input-245-56f3c072cb13> in <module>
# ----> 1 del student[-1]
#
# TypeError: 'tuple' object doesn't support item deletion
student[-1].clear()
student
# ('Tom', 22, 'male', [])
虽然可以修改元组中的列表,但是不能删除该列表,可以将列表清空。
range
range
类型表示的是不可变的数字序列,主要用在 for
循环中
# range(stop):单个参数表示从 0 到 (stop-1)
list(range(10))
# [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
# range(start, stop[, step])
list(range(1, 11))
# [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
list(range(0, 30, 5))
# [0, 5, 10, 15, 20, 25]
list(range(0, -10, -1))
# [0, -1, -2, -3, -4, -5, -6, -7, -8, -9]
list(range(1, 0))
# []
range 的优势在于总是占用固定数量(较小)的内存,不论其表示的范围多大,会存储 start
, stop
和 step
的值,并在需要的时候计算相应的值
r = range(0, 10, 2)
r
# range(0, 10, 2)
5 in r
# False
2 in r
# True
r[2]
# 4
r[2:]
# range(4, 10, 2)
r.index(4)
# 2
r.count(5)
# 0
str
str
也属于不可变序列类型,除了包含与元组类似的方法之外,还有许多字符串处理方法,详细内容将会在后面的章节中介绍。
迭代与解析
for i in [1, 3, 6, 9]:
print(i, end=' ')
# 1 3 6 9
# enumerate 函数可以同时返回列表的索引及其对应的元素
for i, e in enumerate([1, 3, 6, 9]):
print(i, e)
# 0 1
# 1 3
# 2 6
# 3 9
如果想获取一个列表的子集,例如,获取 10
以内的奇数
odd = []
for i in range(10):
if i % 2 != 0:
odd.append(i)
odd
# [1, 3, 5, 7, 9]
Python
提供了一种列表解析(也叫列表推导式)的方式,以一种更简洁的方式来创建列表。可以将 for
循环以及 if...else
的代码放在中括号内
# 只使用 if,将 if 语句放后面
odd = [i for i in range(10) if i % 2 != 0]
odd
# [1, 3, 5, 7, 9]
# if else 放置在前面
[(i, 'odd') if i % 2 != 0 else (i, 'even') for i in range(10)]
# [(0, 'even'),
# (1, 'odd'),
# (2, 'even'),
# (3, 'odd'),
# (4, 'even'),
# (5, 'odd'),
# (6, 'even'),
# (7, 'odd'),
# (8, 'even'),
# (9, 'odd')]
解包与压包
所谓解包,可以理解为将容器内的元素分离为一个个独立的单元。例如
# 将列表的每个元素赋值给对应位置的变量
a, b, c = [1, 'A', [1, 2, 3]]
c
# [1, 2, 3]
使用 *
对变量解包
# 解包变量后用于接收中间的值,即将中间值赋值给了该变量
first, *median, last = [1, 5, 6, 0, 1]
# 第一个值
first
# 1
# 变量的值为列表
median
# [5, 6, 0]
# 合并变量
[first, median]
# [1, [5, 6, 0]]
# 对列表解包,可以合并两个列表
[first, *median]
# [1, 5, 6, 0]
有解包,那就也有对应的压包,使用内置函数 zip
对变量进行压缩,并返回一个迭代对象。将输入序列对应位置的元素压缩为一个元组,如果序列长度不一致,则组合的长度为最短序列的长度
a = [1, 2, 3, 4, 5]
b = ['a', 'b', 'c', 'd']
# 压缩
list(zip(a, b))
# [(1, 'a'), (2, 'b'), (3, 'c'), (4, 'd')]
# 压缩多个序列
list(zip(a, b, 'xyz'))
# [(1, 'a', 'x'), (2, 'b', 'y'), (3, 'c', 'z')]
# 同时使用解包与压包
for i, j in zip(a, b):
print(i, j)
# 1 a
# 2 b
# 3 c
# 4 d
矩阵结构
矩阵数据结构类似于二维数组,但是提供了更多的矩阵运算,而且矩阵的元素必须是同类型的。矩阵运算在机器学习领域是非常重要的,而我们许多常见的数据框类型的数据操作也都是基于矩阵来实现的,所以有必要先介绍一下矩阵的一些基本操作。
想要在 Python
中进行矩阵操作和运算,可以使用一个优秀的科学计算扩展库 NumPy(Numerical Python)
。
NumPy
是 Python
数据科学领域的核心库,它提供了一个高性能的多维数组对象,以及大量用于数组运算的函数,很多其他科学计算库都是基于它来实现的,如 scipy
、pandas
、scikit-learn
等,配合 matplotlib
等优秀的绘图库,让 Python
在数据科学领域表现得越加耀眼,备受数据科学家和机器学习爱好者推崇。
NumPy
的核心是 ndarray
对象,它封装了 Python
中原生的同类型数据的 n
维数组,并且许多操作的代码都是在编译后再执行的,用以提高代码的性能。相较于内置的序列对象,NumPy
的执行效率更高(得益于底层的 C/C++
和 Fortran
代码),且代码更少。
pip
安装
pip install numpy -i https://pypi.tuna.tsinghua.edu.cn/simple
conda
安装
conda install numpy
导入
import numpy as np # 1.21.5
数据类型
NumPy
支持的数据类型比 Python
内置类型更多,基本上与 C
语言的数据类型一一对应,设置更合适的数据类型,优化数据存储,提高运行效率。
数据类型对照表
类型代码 | 标识符 | 描述 |
---|---|---|
? | bool_ | 布尔值,True 或 False |
i1/i2/i4/i8 或 b/h/i/l | int8/int16/int32/int64 | 分别表示 8/16/32/64 位有符号整数 |
u1/u2/u4/u8 或 B/H/I/L | uint8/uint16/uint32/uint64 | 分别表示 8/16/32/64 位无符号整数 |
f2/f4/f8/f16 或 e/f/d/g | float16/float32/float64/float128 | 分别表示 16/32/64/128 位浮点数 |
c8/c16/c32 或 F/D/G | complex64/complex128/complex256 | 实部和虚部为 32/64/128 位浮点数的复数 |
S | string_ | 形如 'S#' ,# 表示字节串的长度 |
O | object | 任何 Python 对象 |
U | unicode_ 或 str | 形如 'U#' ,# 表示 unicode 字符的个数 |
M | datetime64 | 标准库 datetime.datetime 类型 |
m | timedelta64 | 标准库 datetime.timedelta 类型 |
还有 4
种代表字节顺序的字符:
=
: 系统原生<
: 小端字节序>
:大端字节序|
: 未应用
所有内置的数据类型对象都是 '='
或 '|'
的字节序。字节序这种计算机底层概念我们不需要了解太多,能看懂便可,想了解相关知识可以自行查阅资料。
np.array(1, dtype='?')
# array(True)
np.arange(10, dtype='b')
# array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], dtype=int8)
np.arange(10, dtype='u1')
# array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], dtype=uint8)
np.arange(10, dtype='f2')
# array([0., 1., 2., 3., 4., 5., 6., 7., 8., 9.], dtype=float16)
np.array('A', dtype='S')
# array(b'A', dtype='|S1')
np.array([b'0', b'3.14', b'2.96'], dtype='|S3')
# array([b'0', b'3.1', b'2.9'], dtype='|S3')
np.array('A', dtype='U1')
# array('A', dtype='<U1')
np.array(['2007-07-13', '2006-01-13', '2010-08-13'], dtype='M')
# array(['2007-07-13', '2006-01-13', '2010-08-13'], dtype='datetime64[D]')
更多数据类型的内容,可以查阅数组标量文档
创建数组
常用的数组创建方式:
- 转换
Python
内置数据结构(如列表,元组),如果传递的列表类型不一致,会自动按照类型转换顺序进行转换;也可以使用dtype
函数创建结构化数组,并通过字段名称进行访问
a = np.array([1, 3, 6, 9, 1])
# array([1, 3, 6, 9, 1])
type(a)
# numpy.ndarray
np.array([[1, 3, 6], [2, 5., 7]])
# array([[1., 3., 6.],
# [2., 5., 7.]])
np.array([1, '3', 6.0, 9, 1])
# array(['1', '3', '6.0', '9', '1'], dtype='<U21')
np.array([('Tom', 21, 95),('July', 18, 90)])
# array([['Tom', '21', '95'],
# ['July', '18', '90']], dtype='<U4')
# 结构化数组
dt = np.dtype([('name','U20'), ('age', 'i1'), ('score', 'f2')])
a = np.array([('Tom', 21, 95),('July', 18, 90)], dtype=dt)
a
# array([('Tom', 21, 95.), ('July', 18, 90.)],
# dtype=[('name', '<U20'), ('age', 'i1'), ('score', '<f2')])
a['name']
# array(['Tom', 'July'], dtype='<U20')
numpy
提供的函数创建数组(如arange
、ones
、zeros
等),shape
参数指定数组的维度,传入一个元组,元组的长度就是数组的维度
np.ones(shape=(3, 3)) # 创建全为 1 的 3 * 3 矩阵,默认为 float64 类型
# array([[1., 1., 1.],
# [1., 1., 1.],
# [1., 1., 1.]])
np.zeros(shape=(3, 2), dtype=np.int64) # 创建全为 0 的 3 * 2 矩阵,使用 dtype 参数设置数据类型
# array([[0, 0],
# [0, 0],
# [0, 0]])
np.arange(10) # 与内置 range 函数用法类似,多了一个 dtype 参数
# array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
np.linspace(1., 5., 10) # 创建指定数量值的数组
# array([1. , 1.44444444, 1.88888889, 2.33333333, 2.77777778,
# 3.22222222, 3.66666667, 4.11111111, 4.55555556, 5. ])
可以设置数组的 shape
属性或者 reshape
函数来改变数组的维度,接受一个元组,指定每个维度的长度
a = np.arange(1, 13)
a.shape = (3, 4)
a
# array([[ 1, 2, 3, 4],
# [ 5, 6, 7, 8],
# [ 9, 10, 11, 12]])
a.reshape(2, 2, 3)
# array([[[ 1, 2, 3],
# [ 4, 5, 6]],
#
# [[ 7, 8, 9],
# [10, 11, 12]]])
np.linspace(1., 5., 8).reshape((2, 2, 2))
# array([[[1. , 1.57142857],
# [2.14285714, 2.71428571]],
#
# [[3.28571429, 3.85714286],
# [4.42857143, 5. ]]])
- 使用字符串来创建,矩阵可以用专门的函数
np.matrix
来创建
a = np.matrix('1 2; 3 4')
a
# matrix([[1, 2],
# [3, 4]])
np.matrix([[1, 2], [3, 4]])
# matrix([[1, 2],
# [3, 4]])
- 数组属性
NumPy
数组都有一些基本的属性,如
属性 | 描述 | 属性 | 描述 |
---|---|---|---|
ndim | 数组的维度 | T | 数组的转置 |
shape | 数组的形状 | real | 数值元素的实部 |
size | 数组总元素的大小 | imag | 数值元素的虚部 |
dtype | 数组的数据类型 | flat | 数组扁平化为向量,返回迭代器 |
flags | 数组的内存信息 | itemsize | 返回每个元素所占的字节数 |
# 设置随机种子,然固定每次的随机结果
np.random.seed(124)
# 从 1-10 之间挑选整数,创建 2*2 的矩阵
a = np.random.randint(1, 10, size=(2, 2))
a.shape
# (2, 2)
a.ndim
# 2
a.size
# 4
a.dtype
# dtype('int64')
a.flags
# C_CONTIGUOUS : True
# F_CONTIGUOUS : False
# OWNDATA : True
# WRITEABLE : True
# ALIGNED : True
# WRITEBACKIFCOPY : False
# UPDATEIFCOPY : False
a.T
# array([[2, 3],
# [8, 1]])
数组索引
NumPy
中的一维数组的索引与 Python
标准序列类型的索引方式完全一样
a = np.arange(10)
a[0]
# 0
a[-1]
# 9
NumPy
为多维数组提供了多维索引,使用逗号分隔每个维度的索引,每个维度的索引方式与 list
一样。虽然也可以使用嵌套列表的方式,但是这种方式会将每个维度的索引独立在一个中括号内,不太直观
a.shape = (2, 5)
a
# array([[0, 1, 2, 3, 4],
# [5, 6, 7, 8, 9]])
a[0,3]
# 3
a[1,2:-1]
# array([7, 8])
a[0] # 相当于 a[0,:],选择第二维的所有数据可以省略第二维索引
# array([0, 1, 2, 3, 4])
a[0][3]
# 3
可以传入索引数组或能够转换为数组的序列对象来索引,如果是对一维数组索引,返回的结果与索引数组形状一样。因为对于一维数组来说,每个索引值就对应于一个值
a = np.arange(3, 15, 2)
a[np.array([1, 3, 5])]
# array([ 5, 9, 13])
a[[1, 3, 4]]
# array([ 5, 9, 11])
a[np.array([[1, 3, 5], [0, -1, 2]])]
# array([[ 5, 9, 13],
# [ 3, 13, 7]])
而对多维数组索引会更复杂一点,需要多个维度索引值才能确定其对应在数组内的值,其要求每个索引数组的维度一样,或者某一维度可以通过广播机制转换成与其他索引数组一样的维度,通常是将标量广播。将两个索引数组对应位置的值作为配对的维度索引,取出数组中对应位置的值,例如
a = np.arange(1, 13)
a.shape = (3, 4)
a
# array([[ 1, 2, 3, 4],
# [ 5, 6, 7, 8],
# [ 9, 10, 11, 12]])
a[np.array([1, 2]), np.array([1, 3])] # 取出的是索引:(1, 1) 和 (2, 3) 处的值
# array([ 6, 12])
a[1, np.array([0, 1, 3])] # 广播,变成 (1, 0)、(1, 1) 和 (1, 3) 处的值
# array([5, 6, 8])
但是,使用 np.ix_
可以组合 N
个一维数组,生成的结果将是 N
维,每个维度的大小与相应的一维数组长度一致。例如,下面的例子将会生成一个 3*4
的矩阵,结果中的第一行为第一个数组中第一个索引值与第二个数组中所有索引的组合,即 (1, 0)、(1, 3)、(1, 1)、(1, 2)
,以此类推,相当于两个数组的全部组合。
a[np.ix_([1, 2, 0], [0, 3, 1, 2])]
# array([[ 5, 8, 6, 7],
# [ 9, 12, 10, 11],
# [ 1, 4, 2, 3]])
逻辑值索引,如果逻辑索引数组与数组形状一样,会将为 True
位置的值取出,结果为 1
维数组。如果逻辑索引数组的维度更低,则其长度要与要索引维度的长度一致
b = a > 5
b
# array([[False, False, False, False],
# [False, True, True, True],
# [ True, True, True, True]])
a[b]
# array([ 6, 7, 8, 9, 10, 11, 12])
a[b[:,1]] # 索引行,相当于 a[b[:,1], :]
# array([[ 5, 6, 7, 8],
# [ 9, 10, 11, 12]])
a[:, b[1]] # 索引列,相当于 a[:, b[1,:]]
# array([[ 2, 3, 4],
# [ 6, 7, 8],
# [10, 11, 12]])
索引数组与切片联合使用
a[np.array([1, 0]), 2:4]
# array([[7, 8],
# [3, 4]])
a[b[:,1], 2:4]
# array([[ 7, 8],
# [11, 12]])
实际上切片被转换成了索引数组 np.array([2, 3]).reshape((1, 2))
,然后两个索引数组一起广播成 2*2
的矩阵,而不是对应位置索引配对为一个索引。
省略号语法可以选择所有剩余未指定的维度,例如
a = np.arange(8).reshape((2, 2, 2))
a
# array([[[0, 1],
# [2, 3]],
#
# [[4, 5],
# [6, 7]]])
a[..., 1] # 相当于 a[:, :, 1]
# array([[1, 3],
# [5, 7]])
a[0, ...] # 相当于 a[0, :, :]
# array([[0, 1],
# [2, 3]])
由于索引都是数组的引用,可以根据索引位置来修改对应的值,必须保证分配给索引数组对应的值的形状一致或可广播的
a = np.arange(9).reshape(3, 3)
a
# array([[0, 1, 2],
# [3, 4, 5],
# [6, 7, 8]])
a[1,1] = 0
a[0:2, 0] = 5
a[2, 1:3] = [13, 7]
# array([[ 5, 1, 2],
# [ 5, 0, 5],
# [ 6, 13, 7]])
更多操作,可查看索引相关文档
广播机制
当同时操作两个数组时,将会依次从右到左比较两个数组的形状,当两个数组:
- 形状一致
- 或其中一个维度为
1
时
这两个数组是可以相互兼容的,能够执行对应的操作,否则会抛出 ValueError
的异常。最终输出结果的形状将取每次比较的最大值,例如
a = np.arange(30).reshape((5, 1, 6, 1))
b = np.arange(25).reshape(5, 1, 5)
c = a + b
c.shape
# (5, 5, 6, 5)
a = np.arange(30).reshape((2, 5, 3))
b = np.arange(15).reshape(5, 3)
c = a + b
c.shape
# (2, 5, 3)
a = np.arange(5)
b = 2
c = a + b
c.shape
# (5,)
# 无法执行操作的情况
a = np.arange(30).reshape((2, 5, 3))
b = np.arange(12).reshape(4, 3)
c = a + b
# ValueError: operands could not be broadcast together with shapes (2,5,3) (4,3)
a = np.arange(5)
b = np.arange(3)
c = a + b
# ValueError: operands could not be broadcast together with shapes (5,) (3,)
一维数组默认是一行(即行向量),如果要操作两个一维数组,可以将其中一个转置,或者使用 newaxis
为其中一个数组添加一个空轴,将其转换为列向量
a = np.arange(5)
b = np.arange(3).reshape((3,1))
c = a + b
c.shape
# (3, 5)
a[:, np.newaxis] + b
# array([[0, 1, 2],
# [1, 2, 3],
# [2, 3, 4],
# [3, 4, 5],
# [4, 5, 6]])
a + b[:, np.newaxis]
# array([[0, 1, 2, 3, 4],
# [1, 2, 3, 4, 5],
# [2, 3, 4, 5, 6]])
数组操作
- 修改形状
修改形状 | 描述 |
---|---|
reshape | 不改变数据的同时修改形状 |
flatten | 数组扁平化为一维后的新数组 |
ravel | 类似 flatten ,返回视图或拷贝 |
a = np.arange(1, 13).reshape(3, 4)
a.flatten() # 扁平化为一维,返回新的数组
# array([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12])
# 也是扁平化为一维,但是返回的是原数组的视图,所以对视图(可以看成是一种引用)
# 的修改会反映到原数组
b = a.ravel()
b[0] = 7
a
# array([[ 7, 2, 3, 4],
# [ 5, 6, 7, 8],
# [ 9, 10, 11, 12]])
函数 | 描述 | 函数 | 描述 |
---|---|---|---|
moveaxis | 移动轴的新的位置,推荐使用 | swapaxes | 交换数组的两个轴 |
rollaxis | 类似于 moveaxis ,不推荐使用 | expand_dims | 添加新的维度 |
broadcast | 将数组广播成另一个数组的形式 | squeeze | 删除长度为个的维度 |
broadcast_to | 将数组广播成指定形状 |
a = np.ones((3,4,5,6))
np.moveaxis(a, 1, 3).shape
# (3, 5, 6, 4)
np.swapaxes(a, 1, 3).shape
# (3, 6, 5, 4)
a = np.array([1, 2])
a.shape
# (2,)
np.expand_dims(a, axis=1).shape
# (2, 1)
np.broadcast_to(a, (4, 2))
# array([[1, 2],
# [1, 2],
# [1, 2],
# [1, 2]])
a = np.array([1, 2, 3]).reshape(1, 3, 1)
np.squeeze(a)
# array([1, 2, 3])
- 合并数组:合并的所有数组,除合并的维度外,其他维度的形状必须一样
函数 | 描述 |
---|---|
concatenate | 沿着某一维度合并多个数组 |
stack | 将形状相同的多个数组序列在新的维度上合并为一个数组 |
hstack | 按列合并,沿着水平轴方向合并多个数组 |
vstack | 按行合并,沿着竖直轴方向合并多个数组 |
dstack | 按深度合并,沿着第三个维度方向合并多个数组 |
a = np.arange(4).reshape(2, 2)
b = np.array([[7, 8]])
np.concatenate((a, b))
# array([[0, 1],
# [2, 3],
# [7, 8]])
np.concatenate((a, b.T), axis=1)
# array([[0, 1, 7],
# [2, 3, 8]])
np.concatenate((a, b.T), axis=None)
# array([0, 1, 2, 3, 7, 8])
np.stack((a, a, a)).shape
# (3, 2, 2)
np.stack((a, a, a), axis=1).shape
# (2, 3, 2)
np.hstack((a, b.T))
# array([[0, 1, 7],
# [2, 3, 8]])
np.vstack((a, b))
# array([[0, 1],
# [2, 3],
# [7, 8]])
np.dstack((a, a)).shape
# (2, 2, 2)
np.dstack((b, b)).shape
# (1, 2, 2)
- 分割数组
函数 | 描述 |
---|---|
split | 将数组分割为多个子数组 |
hsplit | 将数组沿水平分割为多个子数组 |
vsplit | 将数组沿竖直分割为多个子数组 |
dsplit | 将数组沿第三个维度分割为多个子数组 |
a = np.arange(8).reshape(2, 2, 2)
b = np.arange(9)
np.split(a, 2)
# [array([[[0, 1],
# [2, 3]]]),
# array([[[4, 5],
# [6, 7]]])]
np.split(b, [1, 3, 5]) # 设置每段分割的大小
# [array([0]), array([1, 2]), array([3, 4]), array([5, 6, 7, 8])]
np.hsplit(a, 2)
# [array([[[0, 1]],
#
# [[4, 5]]]),
# array([[[2, 3]],
#
# [[6, 7]]])]
np.vsplit(a, 2)
# [array([[[0, 1],
# [2, 3]]]),
# array([[[4, 5],
# [6, 7]]])]
np.dsplit(a, 2)
# [array([[[0],
# [2]],
#
# [[4],
# [6]]]),
# array([[[1],
# [3]],
#
# [[5],
# [7]]])]
- 添加与删除
函数 | 描述 |
---|---|
append | 往数组末尾添加元素 |
insert | 沿着数组中指定轴,在指定的索引前插入元素 |
delete | 删除数组中的元素 |
unique | 去重 |
a = np.arange(6).reshape(2, 3)
b = np.array([9, 8, 7])
c = np.array([12, 14])
np.append(a, b)
# array([0, 1, 2, 3, 4, 5, 9, 8, 7])
np.append(a, b, axis=0)
# array([[0, 1, 2],
# [3, 4, 5],
# [9, 8, 7]])
np.append(a, c, axis=1)
# array([[ 0, 1, 2, 12],
# [ 3, 4, 5, 14]])
np.insert(a, 0, b, axis=0)
# array([[9, 8, 7],
# [0, 1, 2],
# [3, 4, 5]])
np.insert(a, [-1], c, axis=1)
# array([[ 0, 1, 12, 2],
# [ 3, 4, 14, 5]])
np.insert(a, 2, -1)
# array([ 0, 1, -1, 2, 3, 4, 5])
np.delete(a, 1)
# array([0, 2, 3, 4, 5])
np.unique(np.array([2, 3, 2, 1, 3, 4, 3, 4, 3, 3]))
# array([1, 2, 3, 4])
矩阵运算
- 算术运算
函数 | 描述 |
---|---|
add(+) | 对应元素相加, np.add(a, b) 或 a + b |
subtract(-) | 对应元素相减,np.subtract(a, b) 或 a - b |
multiply(*) | 对应元素相乘,np.multiply(a, b) 或 a * b |
divide(/) | 对应元素相除,np.divide(a, b) 或 a / b |
reciprocal | 每个元素取倒数,np.reciprocal(a) 或 1 / a |
power(**) | 对应元素乘方,np.power(a, b) 或 a ** b |
mod(%) | 对应元素取模,np.mod(a, b) 或 a % b |
np.random.seed(124)
a = np.random.randint(1, 10, size=(2, 2))
# array([[2, 8],
# [3, 1]])
np.random.seed(321)
b = np.random.randint(1, 10, size=(2, 2))
# array([[5, 9],
# [2, 9]])
a + b
# array([[ 7, 17],
# [ 5, 10]])
1 / a
# array([[0.5 , 0.125 ],
# [0.33333333, 1. ]])
a % b
# array([[2, 8],
# [1, 1]])
- 矩阵运算
函数 | 描述 |
---|---|
transpose(T) | 矩阵转置 |
dot(@) | 数组点积,矩阵点积可用 @ 符号 |
inner | 向量内积,如果传入的不是向量,则只算最后一个维度 |
outer | 向量外积 |
trace | 数组的迹 |
linalg.eig | 求矩阵的特征值和特征向量 |
linalg.inv | 矩阵的逆 |
linalg.det | 行列式 |
linalg.matrix_rank | 矩阵的秩 |
linalg.solve | 求解线性方程 |
np.linalg.svd | 奇异值分解 |
a.T
# array([[2, 3],
# [8, 1]])
a @ b
# array([[26, 90],
# [17, 36]])
np.linalg.inv(a)
# array([[-0.04545455, 0.36363636],
# [ 0.13636364, -0.09090909]])
np.linalg.det(a)
# 22.000000000000004
b = np.array([12, 7])
np.linalg.solve(a, b)
# array([2., 1.])
字符串数组
NumPy
提供了向量化的字符串函数库 np.char
,可以将函数应用于所有字符串,这些函数都是基于 Python
内置的字符串函数,例如常用的字符串操作函数
函数 | 描述 | 函数 | 描述 |
---|---|---|---|
add | 两个数组对应位置的字符串连接 | find | 查找字符串中某字符首次出现的位置,找不到返回 -1 |
multiply | 多重连接字符串 | index | 类似 find ,找不到字符会抛出异常 |
center | 所有字符串居中 | startswith | 判断字符串的开头字符 |
capitalize | 所有字符串首字母转换为大写 | endswith | 判断字符串的结尾字符 |
title | 字符串内的单词首字母转换为大写 | count | 统计字符出现的次数 |
lower | 所有字符串转换为小写 | split | 指定分隔符对字符串进行分割 |
upper | 所有字符串转换为大写 | join | 通过指定分隔符来连接数组中的元素 |
strip | 移除字符串开头或者结尾处的特定字符 | replace | 使用新字符串替换字符串中的所有子字符串 |
a = np.array(['perl', 'Java'])
b = np.array(['Python', 'julia'])
np.char.add(a, b)
# array(['perlPython', 'Javajulia'], dtype='<U10')
np.char.multiply(a, 3)
# array(['perlperlperl', 'JavaJavaJava'], dtype='<U12')
c = np.vstack((a, b))
np.char.title(c)
# array([['Perl', 'Java'],
# ['Python', 'Julia']], dtype='<U6')
np.char.find(c, 'a')
# array([[-1, 1],
# [-1, 4]])
np.char.startswith(c, 'P')
# array([[False, False],
# [ True, False]])
np.char.count(c, 'a')
# array([[0, 2],
# [0, 1]])
d = np.array(['Tom-Judy', 'Ben-Jim'])
np.char.split(d, sep='-')
# array([list(['Tom', 'Judy']), list(['Ben', 'Jim'])], dtype=object)
np.char.replace(d, old='-', new='*')
# array(['Tom*Judy', 'Ben*Jim'], dtype='<U8')
np.char.join('-', a)
# array(['p-e-r-l', 'J-a-v-a'], dtype='<U7')
应用函数
函数 | 描述 | 函数 | 描述 |
---|---|---|---|
mean | 计算中位数 | sort | 排序 |
median | 计算算术均值 | argsort | 返回排序后值的索引 |
average | 可计算加权平均 | lexsort | 多序列排序 |
std | 标准差 | partition | 根据排序后的某值进行分区 |
var | 方差 | argpartition | 返回分区后的索引 |
percentile | 百分位数 | nonzero | 返回数组中非零元素的索引 |
ptp | 极差 | where | 返回数组中满足条件的索引 |
sum | 求和 | extract | 返回数组中满足条件的元素 |
amin | 最小值 | argmin | 最小值的索引 |
amax | 最大值 | argmax | 最大值的索引 |
np.random.seed(123)
data = np.random.random(size = (10, 10))
np.mean(data, axis=0)
# array([0.42994863, 0.63988734, 0.4903538 , 0.52251993, 0.58516409,
# 0.45618968, 0.46804454, 0.44938882, 0.45382167, 0.5189703 ])
np.std(data, axis=0)
# array([0.2499639 , 0.26858064, 0.15235826, 0.19359534, 0.27215236,
# 0.23382116, 0.23837671, 0.31806651, 0.26083425, 0.10835996])
np.percentile(data, 25, axis=1)
# array([0.39986475, 0.2226633 , 0.33266635, 0.42596054, 0.34167066,
# 0.36132843, 0.32586402, 0.1545428 , 0.32842421, 0.34657073])
data = np.random.randint(1, 20, size=10)
data
# array([14, 3, 3, 7, 18, 11, 2, 1, 18, 16])
np.sort(data)
# array([ 1, 2, 3, 3, 7, 11, 14, 16, 18, 18])
np.argsort(data)
# array([7, 6, 1, 2, 3, 5, 0, 9, 4, 8])
结构化数组
通常,我们创建的数组都要存放同一类型的数据,而想要存放不同类型的数据,需要定义结构化数组,类似于一种命名字段(可以看成是一种由简单数据类型组合起来的数据结构)的序列。
结构化数据类型的创建
使用 np.dtype
函数来创建结构化数据类型,包含 3
种不同的创建形式:
元组列表
每个元组表示一个字段,包含 3
个数据,形如 (fieldname, datatype, shape)
,其中 fieldname
为字段的名称,可通过名称索引对应的字段值,如果缺省,则会以 fn
的形式自动创建,datatype
为该字段存储的数据类型, shape
为可选的元组数据,用于指示子数组的形状。例如
np.dtype([('name', 'U12'), ('age', np.int8), ('index', 'f2', (2, 2))])
# dtype([('name', '<U12'), ('age', 'i1'), ('index', '<f2', (2, 2))])
np.dtype([('nage', 'U12'), ('', np.int8), ('index', 'f2', (2, 2))])
# dtype([('nage', '<U12'), ('f1', 'i1'), ('index', '<f2', (2, 2))])
fieldname
也可以是一个元组,用于设置字段的标题和字段名称
np.dtype([(('title', 'name'), 'U12'), ('age', np.int8), ('index', 'f2', (2, 2))])
# dtype([(('title', 'name'), '<U12'), ('age', 'i1'), ('index', '<f2', (2, 2))])
逗号分隔的字符串
这种创建方式只需指定数据类型,数据的形状可以使用类型字符前的数值或元组确定,字段名称会自动添加。
np.dtype('U1, ?, S2')
# dtype([('f0', '<U1'), ('f1', '?'), ('f2', 'S2')])
np.dtype('3U1, (2,3)?, S2')
# dtype([('f0', '<U1', (3,)), ('f1', '?', (2, 3)), ('f2', 'S2')])
字典
使用字典的方式来指定不同字段的名称和类型等,形如 {'names': ..., 'formats': ..., 'offsets': ..., 'titles': ..., 'itemsize': ..., 'aligned': ...}
,其中 names
和 formats
为必须的键,其他四个为可选的键。例如
d = np.dtype({
'names': ['name', 'age'],
'formats': ['U12', 'i1']
})
# dtype([('name', '<U12'), ('age', 'i1')])
d.names # 获取字段名
# ('name', 'age')
d.fields # 获取所有字段
# mappingproxy({'name': (dtype('<U12'), 0), 'age': (dtype('int8'), 48)})
d.itemsize # 数据结构的大小
# 49
offsets
指定每个字段的字节偏移量,itemsize
用于指定整个数据结构的大小,必须达到能包含所有的字段,如果未设置,则会自动确定。
d = np.dtype({
'names': ['sex', 'age'],
'formats': ['U1', 'i1'],
'titles': ['Sexuality', 'Age'],
'offset': [0, 4],
'itemsize': 10
})
d.itemsize
# 10
d.fields
# mappingproxy({'sex': (dtype('<U1'), 0, 'Sexuality'),
# 'Sexuality': (dtype('<U1'), 0, 'Sexuality'),
# 'age': (dtype('int8'), 4, 'Age'),
# 'Age': (dtype('int8'), 4, 'Age')})
aligned
用于指定是否将所有的偏移进行对齐。
d = np.dtype({
'names': ['a', 'b', 'c', 'd'],
'formats': ['u1', 'i2', 'f4', '?']
})
d.fields
# mappingproxy({'a': (dtype('uint8'), 0),
# 'b': (dtype('int16'), 1),
# 'c': (dtype('float32'), 3),
# 'd': (dtype('bool'), 7)})
d = np.dtype({
'names': ['a', 'b', 'c', 'd'],
'formats': ['u1', 'i2', 'f4', '?'],
'aligned': True
})
d.fields
# mappingproxy({'a': (dtype('uint8'), 0),
# 'b': (dtype('int16'), 2),
# 'c': (dtype('float32'), 4),
# 'd': (dtype('bool'), 8)})
结构化数组创建与访问
创建结构化数组只需将 dtype
参数设置为结构化类型即可,然后使用字段名来访问对应的字段数据
a = np.array([(1, 2, 3), (4, 5, 6)], dtype='i2, ?, U1')
a
# array([(1, True, '3'), (4, True, '6')],
# dtype=[('f0', '<i2'), ('f1', '?'), ('f2', '<U1')])
a['f1']
# array([ True, True])
a.shape
# (2,)
可以看到,这其实是包含两个结构化对象的一维数组,让我们来创建一个二维的数组
a = np.ones((2, 2), dtype=np.dtype([('a', '?', 3), ('b', 'f2', (2, 2))]))
a
# array([[([ True, True, True], [[1., 1.], [1., 1.]]),
# ([ True, True, True], [[1., 1.], [1., 1.]])],
# [([ True, True, True], [[1., 1.], [1., 1.]]),
# ([ True, True, True], [[1., 1.], [1., 1.]])]],
# dtype=[('a', '?', (3,)), ('b', '<f2', (2, 2))])
a.shape
# (2, 2)
a[0][1]['a']
# array([ True, True, True])
# 使用标量赋值
a[0][1]['b'] = 0
a[0][1]['b']
# array([[0., 0.],
# [0., 0.]], dtype=float16)
# 使用数组赋值
c = np.array([1, 0, 3], dtype='?')
a[0][0]['a'] = c
a[0][0]['a']
# array([ True, False, True])
可以看到,数组的包含两行两列,每个元素都是一个结构化类型的数据,可以使用对应形状的数组或标量为结构化数据赋值
哈希表
哈希表又称散列表,是通过一种 key-value
的形式来存储数据的结构,每个 key
都是唯一的,即通过 key
的值,可以快速的访问到其所对应的存储在内存中的值,提高查找速度。例如,学生可以使用学号来标识,通过学号,我们可以获取该生的信息,如姓名、性别、年龄等。
dict
字典——顾名思义,我们可以通过索引快速的查找并获取对应的信息,这也正是哈希表的功能。Python 中的字典也是一种可变的容器,字典的键必须为不可变对象(通常使用字符串或数字作为键),值可以使任意类型的数据。如果把列表看成是一个有序的集合,那么字典就是一个无序的集合。
创建字典
使用大括号 {}
或 dict
来定义一个字典
a = {
'name': 'Tom',
'age': 18,
'sex': 'male'
}
b = dict(
A = [1, 3, 9, 2],
B = 1.23,
C = a
)
也可以使用 fromkeys
来创建一个值为空的字典,键的默认值为空
dict.fromkeys(['A', 'B'])
# {'A': None, 'B': None}
# 设置默认值
dict.fromkeys(['A', 'B'], 0)
# {'A': 0, 'B': 0}
与列表类似,字典也有相应的字典解析用于创建新的字典,简化代码
{i: i**2 for i in range(5)}
# {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
# if
{i: i**2 for i in range(5) if i % 2 ==0}
# {0: 0, 2: 4, 4: 16}
# if..else,键不变,变的是值
{i: i**2 if i % 2 == 0 else i**3 for i in range(5)}
# {0: 0, 1: 1, 2: 4, 3: 27, 4: 16}
访问字典
访问数组可以使用中括号加键名的方式,或者使用内置的 get
方法。两者之间的区别在于,如果键不存在字典中,中括号的方式会抛出异常,get
方法可以设置返回值,默认返回 None
a['name']
# 'Tom'
a.get('age')
# 18
a['score']
# KeyError: 'score'
# 键不存在默认返回空
a.get('score')
a.get('score', '-')
# '-'
b['C']['sex']
# 'male'
获取字典的键和值
# 获取字典中的所有键
a.keys()
# dict_keys(['name', 'age', 'sex'])
# 获取字典的值
a.values()
# dict_values(['Tom', 18, 'male'])
# 将键和值以元组列表的形式返回
a.items()
# dict_items([('name', 'Tom'), ('age', 18), ('sex', 'male')])
# 计算键的数量
len(a)
# 3
# 判断键是否存在
'A' in b
# True
keys
方法返回的对象类似于集合,可以使用集合操作
# 交集
a.keys() & b.keys()
# set()
# 差集
a.keys() - b.keys()
# {'age', 'name', 'sex'}
# 并集
a.keys() | b.keys()
# {'A', 'B', 'C', 'age', 'name', 'sex'}
修改字典
# 将 age 设置为 21
a['age'] = 21
# 删除键并返回其所对应的值
b.pop('A')
# [1, 3, 9, 2]
# 删除键
del b['C']
b
# {'B': 1.23}
a
# {'name': 'Tom', 'age': 21, 'sex': 'male'}
# 清空字典
b.clear()
b
# {}
合并两个字典
# 添加键-值
b['name'] = 'Sam'
b['A'] = [1, 2, 3, 4]
# 合并两个字典
b.update(a)
# 相同键的值将会被覆盖
b
# {'A': [1, 2, 3, 4], 'name': 'Tom', 'age': 18, 'sex': 'male'}
使用 setdefault
添加新的键,如果未设置值会自动设置为空,如果键已经存在了,只会返回该键对应的值,并不会对值进行修改
# 键存在,返回原来的值
a.setdefault('age', 25)
# 21
# 添加新键及其对应的值
a.setdefault('price', 1000)
# 1000
# 默认设置为 None
a.setdefault('M')
a
# {'name': 'Tom', 'age': 21, 'sex': 'male', 'price': 1000, 'M': None}
字典遍历与解析
# 使用 keys 来遍历键,等同于 for k in a
for k in a.keys():
print(k, a[k])
# name Tom
# age 21
# sex male
# price 1000
# M None
# 使用 items 同时获取键和值
for k, v in a.items():
print(k, v)
# name Tom
# age 21
# sex male
# price 1000
# M None
字典的解包使用两个星号(**
)
{**a, **b}
# {'name': 'Tom',
# 'age': 21,
# 'sex': 'male',
# 'price': 1000,
# 'M': None,
# 'A': [1, 2, 3, 4]}
# 字典解包赋值,返回字典的键
a, b = {'a': 1, 'b': 2}
a
# 'a'
# 使用 items 函数,返回键值对元组
a, b = {'a': 1, 'b': 2}.items()
a
# ('a', 1)
常用技巧
由于字典的值可以是任意的,所以在创建一个空字典时,并不能知道字典键和值的类型,当字典的值需要在程序运行时修改,通常需要在程序中先对键的值进行初始化。例如,统计一串字符串中字符出现的次数
alpha_count = {}
for i in 'ABCABCDFBCDGFCAHJDBFDHABDABCHCAANJD':
if i in alpha_count:
alpha_count[i] += 1
else:
alpha_count[i] = 1
alpha_count
# {'A': 7, 'B': 6, 'C': 6, 'D': 6, 'F': 3, 'G': 1, 'H': 3, 'J': 2, 'N': 1}
我们可以使用标准库 collections
提供的 defaultdict
函数来定义字典键的默认值来简化代码
from collections import defaultdict
alpha_count = defaultdict(lambda : 0)
for i in 'ABCABCDFBCDGFCAHJDBFDHABDABCHCAANJD':
alpha_count[i] += 1
print(alpha_count)
# defaultdict(<function <lambda> at 0x7f18c62b84c0>, {'A': 7, 'B': 6, 'C': 6, 'D': 6, 'F': 3, 'G': 1, 'H': 3, 'J': 2, 'N': 1})
当然,对于这个统计计数问题,可以使用该库中的另一个函数 Counter
直接计算
from collections import Counter
count = Counter('ABCABCDFBCDGFCAHJDBFDHABDABCHCAANJD')
count
# Counter({'A': 7,
# 'B': 6,
# 'C': 6,
# 'D': 6,
# 'F': 3,
# 'G': 1,
# 'H': 3,
# 'J': 2,
# 'N': 1})
count.most_common(3) # 取出频率最高的前三个字符及其频数
# [('A', 7), ('B', 6), ('C', 6)]
由于 defaultdict
需要传递一个可调用对象,所以我们封装成 lambda
函数,如果默认值是列表,则只需传入 list
file_format = defaultdict(list)
for f in ['a.txt', 'b.csv', 'test.txt', 'out.py']:
suffix = f.split('.')[-1] # 对字符串按 . 拆分为一个列表,返回最后一个值
file_format[suffix].append(f)
file_format
# defaultdict(list,
# {'txt': ['a.txt', 'test.txt'], 'csv': ['b.csv'], 'py': ['out.py']})
set
Python
中的集合可以与数学中的集合概念合并,即一个无序且不重复的序列。
创建集合
集合可以使用大括号 { }
和 set
函数来创建,注意,空集合只能使用 set
函数创建,因为 { }
创建的是一个空字典。
a = {1, 'A', 3.14}
a
# {1, 3.14, 'A'}
set(range(10))
# {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
集合也有类似于列表推导式的快捷方法,称为集合推导式
{i for i in range(10) if i % 2 == 0}
# {0, 2, 4, 6, 8}
集合的一个常用功能就是对列表去重
set([1 if i % 2 == 0 else 0 for i in range(10)])
# {0, 1}
添加元素
逐一添加元素
a = set()
for i in range(10):
a.add(i)
a
# {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
一次添加多个
a.update({'A', 'C'})
a
# {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 'A', 'C'}
a.update([-1, 0], [1.32, 'b'])
a
# {-1, 0, 1, 1.32, 2, 3, 4, 5, 6, 7, 8, 9, 'A', 'C', 'b'}
删除元素
使用 remove
可以将元素从集合中删除,但是删除不存在的元素会抛出异常
a.remove(5)
a
# {-1, 0, 1, 1.32, 2, 3, 4, 6, 7, 8, 9, 'A', 'C', 'b'}
a.remove(5)
# ---------------------------------------------------------------------------
# KeyError Traceback (most recent call last)
# <ipython-input-13-fa8c667230ef> in <module>
# ----> 1 a.remove(5)
# KeyError: 5
使用 discard
删除不存在的元素不会抛出异常,而是返回空
a.discard(6)
a
# {-1, 0, 1, 1.32, 2, 3, 4, 7, 8, 9, 'A', 'C', 'b'}
a.discard(6)
使用 pop
删除元素,但是删除哪个元素是不可预期的
for i in range(3):
a.pop()
print(a)
# {-1, 1, 1.32, 2, 3, 4, 7, 8, 9, 'A', 'C', 'b'}
# {-1, 1.32, 2, 3, 4, 7, 8, 9, 'A', 'C', 'b'}
# {-1, 1.32, 3, 4, 7, 8, 9, 'A', 'C', 'b'}
集合运算
集合运算可以使用运算符,包括交集(&
)、并集(|
)、差集(-
)、对称差(^
)
a = {1, 3, 9, 'A', 'C', 'b'}
b = {4, 3, 'B', 'C'}
a & b # 交集
# {3, 'C'}
a | b # 并集
# {1, 3, 4, 9, 'A', 'B', 'C', 'b'}
a - b # 差集
# {1, 9, 'A', 'b'}
a ^ b # 对称差
# {1, 4, 9, 'A', 'B', 'b'}
除了可以使用运算符进行集合运算,还可以使用对应的方法
a.intersection(b) # 交集
# {3, 'C'}
a.union(b) # 并集
# {1, 3, 4, 9, 'A', 'B', 'C', 'b'}
a.difference(b) # 差集
# {1, 9, 'A', 'b'}
a.symmetric_difference(b) # 对称差
# {1, 4, 9, 'A', 'B', 'b'}
除了 union
之外,其他三个方法都有一个 update
版本,其区别在于会对集合进行修改而不是返回一个新的集合
a.intersection_update(b)
a
# {3, 'C'}
a.add(0)
a.add('E')
a.difference_update(b)
a
# {0, 'E'}
a.add(3)
a.symmetric_difference_update(b)
a
# {0, 4, 'B', 'C', 'E'}
集合关系运算
a = {3, 'C'}
b = {4, 3, 'B', 'C'}
a.issubset(b) # 判断 a 是否为 b 的子集
# True
b.issuperset(a) # b 是否为 a 的超集
# True
a.isdisjoint(b) # 判断两个集合是否存在交集
# False