• Python基本语法(3)开数组


    从 C++ 转过来的同学可能很迷惑怎么在 Python 中开数组,这里就介绍在 Python 开「数组」的语法,需要强调我们介绍的其实是几种 序列类型,和 C 的数组有着本质区别,而更接近 C++ 中的 vector

    使用 list

    列表(list)大概是 Python 中最常用也最强大的序列类型,列表中可以存放任意类型的元素,包括嵌套的列表,这符合数据结构中「广义表」的定义。请注意不要将其与 C++ STL 中的双向链表 list 混淆,故本文将使用「列表」而非 list 以免造成误解。

    1. >>> [] # 创建空列表,注意列表使用方括号
    2. []
    3. >>> nums = [0, 1, 2, 3, 5, 8, 13]; nums # 初始化列表,注意整个列表可以直接打印
    4. [0, 1, 2, 3, 5, 8, 13]
    5. >>> nums[0] = 1; nums # 支持索引访问,还支持修改元素
    6. [1, 1, 2, 3, 5, 8, 13]
    7. >>> nums.append(nums[-2]+nums[-1]); nums # append() 同 vector 的 push_back(),也都没有返回值
    8. [1, 1, 2, 3, 5, 8, 13, 21]
    9. >>> nums.pop() # 弹出并返回末尾元素,可以当栈使用;其实还可指定位置,默认是末尾
    10. 21
    11. >>> nums.insert(0, 1); nums # 同 vector 的 insert(position, val)
    12. [1, 1, 1, 2, 3, 5, 8, 13]
    13. >>> nums.remove(1); nums # 按值移除元素(只删第一个出现的),若不存在则抛出错误
    14. [1, 1, 2, 3, 5, 8, 13]
    15. >>> len(nums) # 求列表长度,类似 vector 的 size(),但 len() 是内置函数
    16. 7
    17. >>> nums.reverse(); nums # 原地逆置
    18. [13, 8, 5, 3, 2, 1, 1]
    19. >>> sorted(nums) # 获得排序后的列表
    20. [1, 1, 2, 3, 5, 8, 13]
    21. >>> nums # 但原来的列表并未排序
    22. [13, 8, 5, 3, 2, 1, 1]
    23. >>> nums.sort(); nums # 原地排序,可以指定参数 key 作为排序标准
    24. [1, 1, 2, 3, 5, 8, 13]
    25. >>> nums.count(1) # 类似 std::count()
    26. 2
    27. >>> nums.index(1) # 返回值首次出现项的索引号,若不存在则抛出错误
    28. 0
    29. >>> nums.clear(); nums # 同 vector 的 clear()

    以上示例展现了列表与 vector 的相似之处,vector 中常用的操作一般也都能在列表中找到对应方法,不过某些方法如 len(),sorted() 会以内置函数的面目出现,而 STL 算法中的函数如 find(),count(),max_element(),sort(),reverse() 在 Python 中又成了对象的方法,使用时需要注意区分,更多方法请参见官方文档的 列表详解。下面将展示列表作为 Python 的基本序列类型的一些强大功能:

    Python 支持多种复合数据类型,可将不同值组合在一起。最常用的 list,类型是用方括号标注、逗号分隔的一组值。例如,[1, 2, 3] 和 ['a','b','c'] 都是列表。

    1. >>> lst = [1, '1'] + ["2", 3.0] # 列表直接相加生成一个新列表
    2. >>> lst # 这里存放不同的类型只是想说明可以这么做,但这不是好的做法
    3. [1, '1', '2', 3.0]
    4. >>> 3 in lst # 实用的成员检测操作,字符串也有该操作且还支持子串检测
    5. True
    6. >>> [1, '1'] in lst # 仅支持单个成员检测,不会发现「子序列」
    7. False
    8. >>> lst[1:3] = [2, 3]; lst # 切片并赋值,原列表被修改
    9. [1, 2, 3, 3.0]
    10. >>> lst[::-1] # 获得反转后的新列表
    11. [3.0, 3, 2, 1]
    12. >>> lst *= 2; lst # 数乘拼接
    13. [1, 2, 3, 3.0, 1, 2, 3, 3.0]
    14. >>> del lst[4:]; lst # 也可写 lst[4:] = [],del 语句不止可以用于删除序列中元素
    15. [1, 2, 3, 3.0]

     以上示例展现了列表作为序列的一些常用操作,可以看出许多操作如切片是与字符串相通的,但字符串是「不可变序列」而列表是「可变序列」,故可以通过切片灵活地修改列表。在 C/C++ 中我们往往会通过循环处理字符数组,下面将展示如何使用 「列表推导式」 在字符串和列表之间转换:

    1. >>> # 建立一个 [65, 70) 区间上的整数数组,range 也是一种类型,可看作左闭右开区间,第三个参数为步长可省略
    2. >>> nums = list(range(65,70)) # 记得 range 外面还要套一层 list()
    3. [65, 66, 67, 68, 69]
    4. >>> lst = [chr(x) for x in nums] # 列表推导式的典型结构,[exp for var in iterable if cond]
    5. >>> lst # 上两句可以合并成 [str(x) for x in range(65,70)]
    6. ['A', 'B', 'C', 'D', 'E']
    7. >>> s = ''.join(lst); s # 用空字符串 '' 拼接列表中的元素生成新字符串
    8. 'ABCDE'
    9. >> list(s) # 字符串生成字符列表
    10. ['A', 'B', 'C', 'D', 'E']
    11. >>> # 如果你不知道有 s.lower() 方法就可能写出下面这样新瓶装旧酒的表达式
    12. >>> ''.join([chr(ord(ch) - 65 + 97) for ch in s if ch >= 'A' and ch <= 'Z'])
    13. 'abcde'

    下面演示一些在 OI 中更常见的场景,比如二维「数组」:

    1. >>> vis = [[0] * 3] * 3 # 开一个3*3的全0数组
    2. >>> vis
    3. [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
    4. >>> vis[0][0] = 1; vis # 怎么会把其他行也修改了?
    5. [[1, 0, 0], [1, 0, 0], [1, 0, 0]]
    6. >>> # 先来看下一维列表的赋值
    7. >>> a1 = [0, 0, 0]; a2 = a1; a3 = a1[:] # 列表也可以直接被赋给新的变量
    8. >>> a1[0] = 1; a1 # 修改列表 a1,似乎正常
    9. [1, 0, 0]
    10. >>> a2 # 怎么 a2 也被改变了
    11. [1, 0, 0]
    12. >>> a3 # a3 没有变化
    13. [0, 0, 0]
    14. >>> id(a1) == id(a2) and id(a1) != id(a3) # 内置函数 id() 给出对象的「标识值」,可类比为地址,地址相同说明是一个对象
    15. True
    16. >>> vis2 = vis[:]; # 拷贝一份二维列表看看
    17. >>> vis[0][1] = 2; vis # vis 肯定还是被批量修改
    18. >>> [[1, 2, 0], [1, 2, 0], [1, 2, 0]]
    19. >>> vis2 # 但 vis2 是切片拷贝的怎么还是被改了
    20. >>> [[1, 2, 0], [1, 2, 0], [1, 2, 0]]
    21. >>> id(vis) != id(vis2) # vis 和 vis2 确实不是一个对象啊
    22. True
    23. >>> # 谜底揭晓,vis2 虽然不是 vis 的引用,但其中对应行都指向相同的对象
    24. >>> [[id(vis[i]) == id(vis2[i]) for i in range(3)]
    25. [True, True, True]
    26. >>> # 回看二维列表自身
    27. >>> [id(x) for x in vis] # 具体数字和这里不一样但三个值一定相同,说明是三个相同对象
    28. [139760373248192, 139760373248192, 139760373248192]

    其实我们一直隐瞒了一个重要事实,Python 中赋值只传递了引用而非创建新值,你可以创建不同类型的变量并赋给新变量,验证发现二者的标识值是相同的,只不过直到现在我们才介绍了列表这一种可变类型,而给数字、字符串这样的不可变类型赋新值时实际上创建了新的对象,故而前后两个变量互不干扰。但列表是可变类型,所以我们修改一个列表的元素时,另一个列表由于指向同一个对象所以也被修改了。创建二维数组也是类似的情况,示例中用乘法创建二维列表相当于把 [0]*3 这个一维列表重复了 3 遍,所以涉及其中一个列表的操作会同时影响其他两个列表。更不幸的是,在将二维列表赋给其他变量的时候,就算用切片来拷贝,也只是「浅拷贝」,其中的元素仍然指向相同的对象,解决这个问题需要使用标准库中的 deepcopy,或者尽量避免整个赋值二维列表。不过还好,创建二维列表时避免创建重复的列表还是比较简单,只需使用「列表推导式」:

    1. >>> vis1 = [[0] * 3 for _ in range(3)] # 把用不到的循环计数变量设为下划线 _ 是一种惯例
    2. >>> # 但在 REPL 中 _ 默认指代上一个表达式输出的结果,故也可使用双下划线
    3. >>> vis1
    4. [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
    5. >>> [id(x) for x in vis1] # 具体数字和这里不一样但三个值一定不同,说明是三个不同对象
    6. [139685508981248, 139685508981568, 139685508981184]
    7. >>> vis1[0][0] = 1
    8. [[1, 0, 0], [0, 0, 0], [0, 0, 0]]
    9. >>> a2[0][0] = 10 # 访问和赋值二维数组

    我们未讲循环的用法就先介绍了列表推导式,这是由于 Python 是高度动态的解释型语言,因此其程序运行有大量的额外开销。尤其是 for 循环在 Python 中运行的奇慢无比。因此在使用 Python 时若想获得高性能,尽量使用使用列表推导式,或者 filter,map 等内置函数直接操作整个序列来避免循环,当然这还是要根据具体问题而定。

    使用 NumPy

    什么是 NumPy

    NumPy 是著名的 Python 科学计算库,提供高性能的数值及矩阵运算。在测试算法原型时可以利用 NumPy 避免手写排序、求最值等算法。NumPy 的核心数据结构是 ndarray,即 n 维数组,它在内存中连续存储,是定长的。此外 NumPy 核心是用 C 编写的,运算效率很高。不过需要注意,它不是标准库的一部分,可以使用 pip install numpy 安装,但不保证 OI 考场环境中可用。

    下面的代码将介绍如何利用 NumPy 建立多维数组并进行访问。

    1. >>> import numpy as np # 请自行搜索 import 的意义和用法
    2. >>> np.empty(3) # 开容量为3的空数组,注意没有初始化为0
    3. array([0.00000000e+000, 0.00000000e+000, 2.01191014e+180])
    4. >>> np.zeros((3, 3)) # 开 3*3 的数组,并初始化为0
    5. array([[0., 0., 0.],
    6. [0., 0., 0.],
    7. [0., 0., 0.]])
    8. >>> a1 = np.zeros((3, 3), dtype=int) # 开3×3的整数数组
    9. >>> a1[0][0] = 1 # 访问和赋值
    10. >>> a1[0, 0] = 1 # 更友好的语法
    11. >>> a1.shape # 数组的形状
    12. (3, 3)
    13. >>> a1[:2, :2] # 取前两行、前两列构成的子阵,无拷贝
    14. array([[1, 0],
    15. [0, 0]])
    16. >>> a1[0, 2] # 获取第 1、3 列,无拷贝
    17. array([[1, 0],
    18. [0, 0],
    19. [0, 0]])
    20. >>> np.max(a1) # 获取数组最大值
    21. 1
    22. >>> a1.flatten() # 将数组展平
    23. array([1, 0, 0, 0, 0, 0, 0, 0, 0])
    24. >>> np.sort(a1, axis = 1) # 沿行方向对数组进行排序,返回排序结果
    25. array([[0, 0, 1],
    26. [0, 0, 0],
    27. [0, 0, 0]])
    28. >>> a1.sort(axis = 1) # 沿行方向对数组进行原地排序

    使用 array

    array 是 Python 标准库提供的一种高效数值数组,可以紧凑地表示基本类型值的数组,但不支持数组嵌套,也很少见到有人使用它,这里只是顺便提一下。

    若无特殊说明,后文出现「数组」一般指「列表」。

  • 相关阅读:
    记录python 使用SURF SIRF的问题
    面试必备:消息队列原理和选型(荣耀典藏版)
    数据分析---开发环境
    在 Linux 中查找文件的 4 种方式
    Vue学习之--------Vue中自定义插件(2022/8/1)
    vuedraggable插件在弹框中使用时,遇到了哪些问题
    设计模式之策略模式
    uni-app监听页面滚动
    深度学习——第1章 深度学习的概念及神经网络的工作原理
    11. 常用类
  • 原文地址:https://blog.csdn.net/cyyyyds857/article/details/127714561