Python学习笔记19:列表 III

其实这篇笔记标题应该是列表扩展,从列表开始,将涵盖Python中的序列容器。

关于列表的基础知识,可以看我的前两篇文章:

  • Python学习笔记1:列表
  • Python学习笔记17:列表II

我们知道,Python的预设容器可以分为有序和无序两大类,而有序容器中最常见的是列表和元组。

在这个概念之上,我们可以将所有有序的数据模型称为序列。

以是否能容纳复杂数据类型为标准,我们可以把序列分为两大类,容器序列和扁平序列。

容器序列

容器序列,顾名思义,这类数据模型在保持顺序的基础上,可以容纳复杂的数据类型。

这其中最通用和熟悉的就是列表了。

列表

列表的大部分用法都已经在前边的笔记中介绍过了,这其中最有意思的用法是推导式和生成器。

  • 推导式的内容可以看Python学习笔记15:推导式

  • 生成器的内容可以看Python学习笔记16:生成器

对于推导式和生成器,除了基本用法,这里还有一个特殊用法之前没有涉及到。

生成笛卡尔积

除了用推导式和生成器生成一个一维列表,我们还可以生成笛卡尔积。

我有限的剩余的那点高中数学知识告诉我,笛卡尔积就是两个集合中的元素两两组合,求最终的所有可能结果。

我们来看如何用推导式生成笛卡尔积:

listA = ['a', 'b', 'c']
listB = [1, 2, 3]
result = [(a, b) for a in listA for b in listB]
print(result)

输出

[(‘a’, 1), (‘a’, 2), (‘a’, 3), (‘b’, 1), (‘b’, 2), (‘b’, 3), (‘c’, 1), (‘c’, 2), (‘c’, 3)]

可以看到生成了一个3*3的结果集。

我们需要注意到,生成顺序是与推导式中两个for表达式的前后顺序有关,如果我们要先基于listB来遍历生成,只需要修改为[(b,a) for b in ListB for a in ListA]这样即可。

类似的,生成器表达式也可以用于生成笛卡尔积。

listA = ['a', 'b', 'c']
listB = [1, 2, 3]
results = []
for result in ((a, b) for a in listA for b in listB):results.append(result)
print(results)

写法与推导式极为相似,不过需要注意的是,因为生成器是“一个个生成”,所以必须用在迭代中。

排序

排序也是一个在有序容器中很常见的问题。Python中提供两个内置函数sortsorted用于处理排序。

sort

sort用于对有序数据模型直接排序,即会改变当前序列。

from random import randint
listA = [randint(1, 100) for i in range(0, 10)]
print(listA)
listA.sort()
print(listA)

输出

[59, 5, 82, 10, 86, 14, 91, 30, 78, 74]
[5, 10, 14, 30, 59, 74, 78, 82, 86, 91]

sorted

sortedsort不同,它不会改变原本的序列,而是会生成一个排序后的副本。

from random import randint
listA = [randint(1, 100) for i in range(0, 10)]
print(sorted(listA))
print(listA)

输出

[10, 13, 15, 26, 32, 52, 52, 71, 78, 86]
[86, 13, 10, 78, 32, 52, 26, 71, 15, 52]

你可能注意到了,sortsorted的使用方式并不相同,sort是序列的方法,而sorted更像是预设函数。其实这种不同是Python这门语言特色的体现,说的直白点就是一切都向易用看齐。注意,是易用而非易学。

我觉得很多初学者都被Python随意的变量使用和写法欺骗了,误以为这是门很容易学的语言,然而这一切仅仅是为了方便使用,就像sorted,只要是实现了几个预设的魔术方法,不管你的容器长啥样,都可以用sorted来排序,这无疑是相当方便使用的,至于这对学习是否容易,那从来不是这门语言的创建者所考虑的。

key

除了以上的常见用法,Python的内置排序还支持指定key作为排序基准。

我们用以下示例说明:

listA = ["sdfsdf", "eaw", "dfwe", "aqwe", "kersfsq"]
print(sorted(listA))
print(sorted(listA, key=len))

输出

[‘aqwe’, ‘dfwe’, ‘eaw’, ‘kersfsq’, ‘sdfsdf’]
[‘eaw’, ‘dfwe’, ‘aqwe’, ‘sdfsdf’, ‘kersfsq’]

可以看到,我们通过key制定了一个用于排序的基准,取代了默认的基准。需要注意的是key参数必须是一个只接受一个参数的方法,这个方法用于处理序列中的元素,处理后再基于结果值进行排序。

二分查找

如果你接触过排序算法,那肯定对二分查找不陌生。

所谓的二分查找,就是基于一组已排好序的数据,对一个新的元素,快速找出这个元素应当插入的部位,这个部位必须保持已有的顺序。

二分查找的精髓是分治思想,即每次查找的时候都会缩小数据处理规模。

比如第一次,需要找出中位数进行比较,如果新元素小于中位数,就在中位数左侧的一半数据中查找位置,反之就用右侧数据。一次处理就可以让数据比对规模减半。

虽然这种模式在极端情况下,比如新元素刚好小于中位数或大于中位数的时候,效率偏低。但总体来说效率依然客观,是个不错的算法。

Python原生支持二分查找,我们来看代码实现:

import bisect
listA = [0, 1, 2, 3, 4, 5, 6]
index = bisect.bisect(listA, 3)
indexL = bisect.bisect_left(listA, 3)
indexR = bisect.bisect_right(listA, 3)
print(index)
print(indexL)
print(indexR)

输出

4
3
4

可以看到,使用bisect模块可以很容易实现二分查找。

这里有个细节需要注意,在查找算法实现中,如果遇到新元素与已有元素相等的情况,我们通常要考虑是左插入还是右插入的问题,而bisect默认是右插入,这点在上边示例中很容看出。

当然,二分查找的通常目的是找到正确位置后进行插入操作,这点bisect是支持的。

import bisect
listA = [0, 1, 2, 3, 4, 5, 6]
bisect.insort(listA, 3)
print(listA)

输出

[0, 1, 2, 3, 3, 4, 5, 6]

当然,之前也说过了,二分查找是在排序算法中的内容,当然可以用二分查找来实现一个二分排序:

import random
import bisectdef bisectSort(listA: list) -> list:sortedList = []for num in listA:bisect.insort(sortedList, num)return sortedListlistA = [random.randint(1, 100) for i in range(0, 10)]
print(listA)
print(bisectSort(listA))

当然,这里只是用一个示例说明如何用二分查找实现二分排序,Python的内置排序函数sort的综合效率是要优于纯粹的二分查找算法的。

切片

在之前的文章Python学习笔记1:列表中我介绍过如何用切片快速访问列表中的某一段数据,和其它流行语言比起来,这无疑很酷很高效,但切片能做的不仅仅如此。

我们来看下面的示例:

listA = ['a', 'b', 'c', 'd', 'e']
listA[1:4] = ['d', 'c', 'b']
print(listA)

输出

[‘a’, ‘d’, ‘c’, ‘b’, ‘e’]

在这个示例中我们“创造性”地把切片用于赋值操作地左侧,实现了修改某一段子序列地用途。

当然,我们还可以指定步进,间隔着修改序列:

listA = ['a', 'b', 'c', 'd', 'e']
listA[::2] = ['z', 'z', 'z']
print(listA)

输出

[‘z’, ‘b’, ‘z’, ‘d’, ‘z’]

元组

我们通常对Python中元组的认识就是不可变序列,但其实除了不可变序列,元组也可以承担类似字典的任务。

具名元组

除了常用的列表元组等,Python还提供一些其它有用的容器,这些容器都在标准库的collections模块。

这其中有个namedtuple,即具名元组,它是一个工厂函数,可以返回一个类,这个类将实现类似字典的数据结构。

from collections import namedtuple
Person = namedtuple("Person", ("name", "age", "career", "favorite"))
Jack = Person("Jack chen", 17, "actor", ("swimming", "running"))
Brus = Person("Brus Lee", 20, "engineer", ("football", "table tennis"))
print(Jack)
print(Brus)
print(Jack.name)
print(Jack.favorite)
print(Jack[1])
dictJack = Jack._asdict()
print(dictJack)

输出

Person(name=‘Jack chen’, age=17, career=‘actor’, favorite=(‘swimming’, ‘running’))
Person(name=‘Brus Lee’, age=20, career=‘engineer’, favorite=(‘football’, ‘table tennis’))
Jack chen
(‘swimming’, ‘running’)
17
{‘name’: ‘Jack chen’, ‘age’: 17, ‘career’: ‘actor’, ‘favorite’: (‘swimming’, ‘running’)}

我们可以看到,使用具名元组的方式很像是在excel中制表,namedtuple(...)的使用很像是在做一个表头,做好后我们需要做的就是一行一行填充数据,而Jack=Person(...)就是在做这样的工作。

事实上也是如此,我们的工作像是给一组数据结构相似的元组加上了一个表头,这样做的好处是节省存储空间。

想一下,如果我们用字典来替代这里的具名元组,每个字典需要4个key,如果有5个Person数据,那就是需要20个key,而具名元组的字段名是存储在工厂方法生成的Person类上的,所有Person生成的具名元组拥有同样的字段名,也就是说5个具名元组也只需要4个key。

除了上面的优点,具名元组的数据访问也很灵活,既可以用.key这样使用字段名访问数据,也可以用传统的切片来访问。

最后,具名元组还可以使用.asdict()来转换为字典,因为它表现出来的结构和字典基本一致。

不可变序列

元组作为不可变序列,主要用途就是作为数据容器使用。

拆包

拆包其实在之前的文章Python学习笔记17:列表II中已经介绍过了,不过称呼不同,当时使用的是解压,这是《Python Cookbook》中的称呼,《Fluent Python》中称之为拆包

关于拆包的大部分内容已经在前文介绍过了,这里不再赘述。只是在这基础上提供一个额外的示例,用来说明Python的灵活性:

tupleA = ("Jack Chen", 16, "engineer", ("football", "table tennis"))
name, *_, (favorite1, favorite2) = tupleA
print(name, favorite1, favorite2)

输出

Jack Chen football table tennis

我们可以看到,即使是元组嵌套元组,我们也可以直接使用拆包获取嵌套结构中的数据。

当然,不止是赋值语句,拆包还可以用在其它语句中,比如for/in

persons = [("Jack Chen", 16, "engineer", ("football", "table tennis")),("Brus Lee", 20, "actor", ("swimming", "running"))]
for name, *_, (favorite1, favorite2) in persons:print(name, favorite1, favorite2)

输出

Jack Chen football table tennis
Brus Lee swimming running

包含可变容器

虽然我们经常说元组是不可变列表,但这其实指的是元组包含的都是原子变量的情况,如果一个元组包含的是可变容器,那情况就会变得有些微妙。

我们来看一个很有趣的例子:

jack = ("Jack Chen", 16, "engineer", ["football", "table tennis"])
jack[-1] += ["swimming"]
print(jack)

输出

Traceback (most recent call last):
File “d:\workspace\python\test\test.py”, line 2, in
jack[-1] += [“swimming”]
TypeError: ‘tuple’ object does not support item assignment

我们视图扩展元组中的列表,输出错误信息,这似乎是理所应当的?

但我们再看下面这个:

jack = ("Jack Chen", 16, "engineer", ["football", "table tennis"])
jack[-1].extend(["swimming"])
print(jack)

输出

(‘Jack Chen’, 16, ‘engineer’, [‘football’, ‘table tennis’, ‘swimming’])

正常执行。

这两者有何区别?

jack[-1]+=["swimming"]其实是两个动作,显示执行jack[-1]+["swimming"]生成一个新的列表,这是没问题的,但是第二步jack[-1]=new_list就会出现错误,因为元组是不允许内部元素重新赋值的。

但是如果我们使用的是.extend(),在原列表上进行扩展,并不涉及重新赋值,那就没有任何问题。

可以看到,元组并非真的内部元素完全不可变,而是取决于具体情况有所不同。所以最佳方案是尽量减少类似的做法,还是仅把元组作为一个不可变列表使用。如果你需要修改内部元素,那你或许应该一开始就声明为列表,而非元组。

需要特别说明的是,虽然在介绍容器序列时,很多特殊操作都是用列表来举例,但实际上这些操作都支持所有的容器序列。

扁平序列

扁平序列是相对于容器序列而言的,所谓扁平序列,就是内部数据类型统一,而且是原子类型,即不是容器。

这类序列通常是用于数学计算或大数据等,用于专业领域。

与容器序列相比,扁平序列更加特异化,只能存储某一类数据,但是内存占用更少,处理速度更快。

双向队列

队列是一个很常见的数据结构,最经典的是First in first out队列。

Python标准库提供一个双向队列,即相比First in first out,只能一边进一边出,双向队列可以同时在左右两边进行数据压入和抛出。

from collections import deque
myQuen = deque(maxlen=10)
listNum = [i for i in range(0,20)]
myQuen.extend(listNum[:10])
print(myQuen)
myQuen.extend(listNum[10:13])
print(myQuen)
myQuen.extendleft(listNum[13:16])
print(myQuen)
myQuen.pop()
myQuen.insert(2,0)
print(myQuen)

输出

deque([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], maxlen=10)
deque([3, 4, 5, 6, 7, 8, 9, 10, 11, 12], maxlen=10)
deque([15, 14, 13, 3, 4, 5, 6, 7, 8, 9], maxlen=10)
deque([15, 14, 0, 13, 3, 4, 5, 6, 7, 8], maxlen=10)

在示例中,我们可以从左右两边都压入和抛出数据(默认情况是右边)。

通常我们会给队列指定一个最大容量maxlen,在这种情况下,如果压入数据后会超出队列的最大容量,队列就会自动抛弃另一头相应数量的数据。

就像例子中的那样,deque也支持一些非常“不队列”的操作,比如在指定位置插入一个元素。但需要说明的是对于这种对于队列来说非常规的操作,deque是没有进行性能优化的,执行效率比较低。相对而言,对于两头压入和抛出数据,deque的执行效率很高。

数组

我们经常说Python中的列表很像是数组,但它终究不是,列表实际上是一个便于使用和效率考量的折中方案。而这就意味着如果你在某种情况下,需要大规模单一数据类型的运算时,列表并不合适。

这时候,就需要使用数组了,真正的数组。

import array
import random
a = array.array('i', (random.randint(1, 100) for i in range(0, 1000)))
print(a[-1])
fopen = open(mode='wb', file='array.file')
a.tofile(fopen)
fopen.close()
b = array.array('i')
fopen = open(mode='rb', file='array.file')
b.fromfile(fopen, 1000)
fopen.close()
print(b[-1])
print(a == b)

輸出

77
77
True

就像示例中展示的那样,在创建数组时候我们需要像其它语言中那样,指明数据类型。

如果你使用的是VSCode,可以在函数帮助文档中看到数据类型说明。同样的,我们需要像在C++中那样,注意不同的数据类型下存储容量和数据范围。

此外,数组支持从其它数据类型中读取和创建,也支持写入到其它数据类型中,这其中甚至包含文件。

就像示例中展示的那样,我们可以很轻松地把数组写入到二进制文件,或者从二进制文件读取。这样做和以文本形式读取文件相比,不仅空间占用低,写入和读取时间也显著减少。

NumPy

NumPy是一个用于科学计算的强大的第三方模块,中文官网是**https://www.numpy.org.cn/**。

NumPy对矩阵的操作异常强大,我们仅举一个简单的例子说明:

import numpy
nArray = numpy.arange(20)
print(nArray)
print(type(nArray))
print(nArray.shape)
nArray.shape=(4,5)
print(nArray)
print(nArray[2])
print(nArray[:,3])
print(nArray[2][3])
print(nArray.transpose())

输出

[ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19]
<class ‘numpy.ndarray’>
(20,)
[[ 0 1 2 3 4]
[ 5 6 7 8 9]
[10 11 12 13 14]
[15 16 17 18 19]]
[10 11 12 13 14]
[ 3 8 13 18]
13
[[ 0 5 10 15]
[ 1 6 11 16]
[ 2 7 12 17]
[ 3 8 13 18]
[ 4 9 14 19]]

在这个例子中,我们通过NumPy提供的数据类型ndarray轻松把一个一维数组转变为了二维数组,而且可以很容易地进行横向和竖向数据切片。更厉害地是还可以进行矩阵操作,比如最后那个矩阵翻转。

当然NumPy这个库相当强大,这些仅仅是冰山一角,这里仅做一个介绍。

除了以上介绍的几种,扁平序列还包括字节数组、字符串等,这里不做过多介绍,后边有机会再深入讲解。

最后附上一个这部分内容的思维导图:

好了,关于列表的补充内容就介绍到这里了,谢谢阅读。

Python学习笔记19:列表 III相关推荐

  1. Python学习笔记:列表、字典与集合解析(List, Dict and Set Comprehensions)

    Python学习笔记:列表.字典与集合解析(List, Dict and Set Comprehensions) 1.列表解析 最受喜爱的Python语言特性.过滤一个集合的元素生成一个新的列表. 一 ...

  2. Python学习笔记之列表(一)

    1.什么是列表: 列表由一系列按特定顺序排列的元素组成,列表包含多个元素,通常给列表定义名称都为users,或者userList,复数或者是列表的命名来指定名称. 在Python中,用方括号([])来 ...

  3. python学习笔记四-列表

    list列表 序列是Python中最基本的数据结构.序列中的每个元素都分配一个数字 - 它的位置,或索引,第一个索引是0,第二个索引是1,依此类推. 列表是最常用的Python数据类型,它可以作为一个 ...

  4. Python学习笔记之列表切片(六)

    1.切片简单描述 什么是切片:在Python中处理列表的部分元素,称之为切片.创建切片,可指定要使用的第一个元素和最后一个元素的索引,示例代码如下: ​#列表切片lists = ['张学友','刘德华 ...

  5. Python学习笔记之列表(五)

    1.创建数值列表:Python提供了一些工具,可帮助我们有效地处理数字列表,列表非常适合用于存储数字集合. (1).函数range():Python中使用函数range()可生成一组数字,示例代码如下 ...

  6. Python学习笔记之列表(四)

    1.遍历整个列表:在程序中经常要遍历整个列表,对每个元素执行一些操作,通常使用for循环来遍历整个列表. (1).示例代码如下: #遍历整个列表projects = ['java','.net','p ...

  7. python学习笔记 day04 列表增删改查

    写了一个python文件,想在终端中运行:windows+r(或者cmd)输入python+想要运行的py文件所在的目录 即可. pycharm的安装 参考博客: https://www.jiansh ...

  8. python学习笔记四一列表元组字典等

    •python中的内置类型◾str ◾list ◾tuple ◾set ◾dict •list 列表 •一组由有序数据组成的序列◾数据有先后数据 ◾数据可以不是一类数据 •list 的创建◾直接创建, ...

  9. Python学习笔记之列表(三)

    1.组织列表:一个列表往往是无序的,需要我们去排序组织这个列表,Python提供了一些排序组织的方法,下面就来介绍一下吧. (1).sort()方法对列表进行永久性的排序:如果需要按字母反顺序来排序的 ...

最新文章

  1. WebStorm 打开多个项目的方法
  2. js向head中添加js代码
  3. 个人认为不错的句子(part2)--计算机是一种工具,但是如果没有绝对精确的指令,计算机也将一无是处
  4. mcq 队列_人工智能能力问答中的人工智能概率推理(MCQ)
  5. 今天上网感觉有点儿卡,上了路由器一看,数据包狂跳
  6. java单链表选票_Hackerrank Practice
  7. Linux mkdir 与 mkdir -p 的区别
  8. githug-54-git练习
  9. java中排序函数sort()使用,Arrays.sort()和Collections.sort()
  10. Spring Boot ES 实战,直接拿来用!
  11. 传智播客 C/C++学习笔记 在向指针所指空间copy数值,要分配内存
  12. 华电C语言题库(循环中等难度)
  13. python操作word实现小学拼音本功能
  14. 《基本无害的计量经济学》第二章:选择性偏误与随机实验
  15. VS中给qt按钮添加图标
  16. 一款基于springboot开发的经典后台管理系统
  17. 网络收敛是什么意思_收敛是什么意思(什么是收敛性)
  18. MAPI 、 IMAP4、 POP3、 SMTP 都是什么协议?
  19. 如何解压tar.gz文件或是.gz文件
  20. 服务中的mysql叫什么名_mysql 服务器名是什么意思

热门文章

  1. 基于Flink1.14 + Iceberg0.13构建实时数据湖实战
  2. Mac10.14.4 独立显卡 影驰GTX 760 4GB成功驱动
  3. Eclipse背景图片的设置
  4. 怎么查看邮件服务器25端口,更改掉smtp25端口怎么接收邮件
  5. python 参数 *args 和 **kwargs
  6. Tin使用方法 - 创建一个 Tin 应用
  7. Unity关于手游端摇杆移动、摇杆按钮冲突问题
  8. 美剧字幕组高手写的学英语心得
  9. 理解最大期望算法EM的三层境界(1)
  10. Perceptron感知机代码讲解笔记