浅谈Python编程中3个常用的数据结构和算法


Posted in Python onApril 30, 2019

本篇文章将介绍3种常见的数据结构和同数据有关的算法。此外,在collections模块中也包含了针对各种数据结构的解决方案。

Python内置了许多非常有用的数据结构,比如列表(list)、集合(set)以及字典(dictionary)。就绝大部分情况而言,我们可以直接使用这些数据结构。但是,通常我们还需要考虑比如搜索、排序、排列以及筛选等这一类常见的问题。

本篇文章将介绍3种常见的数据结构和同数据有关的算法。此外,在collections模块中也包含了针对各种数据结构的解决方案。

1. 将序列分解为单独的变量

(1) 问题

我们有一个包含 N 个元素的元组或序列,现在想将它分解为N个单独的变量。

(2) 解决方案

任何序列(或可迭代的对象)都可以通过一个简单的赋值操作来分解为单独的变量。唯一的要求是变量的总数和结构要与序列相吻合。例如:

>>> p = (4, 5) 
>>> x, y = p 
>>> x 
4 
>>> y 
5 
>>> 
>>> data = [ 'ACME', 50, 91.1, (2012, 12, 21) ] 
>>> name, shares, price, date = data 
>>> name 
'ACME' 
>>> date 
(2012, 12, 21) 
>>> name, shares, price, (year, mon, day) = data 
>>> name 
'ACME' 
>>> year 
2012 
>>> mon 
12 
>>> day 
21 
>>>

如果元素的数量不匹配,将得到一个错误提示。例如:

>>> p = (4, 5) 
>>> x, y, z = p 
Traceback (most recent call last): 
 File "<stdin>", line 1, in <module> 
ValueError: need more than 2 values to unpack 
>>>

(3) 讨论

实际上不仅仅只是元组或列表,只要对象恰好是可迭代的,那么就可以执行分解操作。这包括字符串、文件、迭代器以及生成器。比如:

>>> s = 'Hello' 
>>> a, b, c, d, e = s 
>>> a 
'H' 
>>> b 
'e' 
>>> e 
'o' 
>>>

当做分解操作时,有时候可能想丢弃某些特定的值。Python并没有提供特殊的语法来实现这一点,但是通常可以选一个用不到的变量名,以此来作为要丢弃的值的名称。例如:

>>> data = [ 'ACME', 50, 91.1, (2012, 12, 21) ] 
>>> _, shares, price, _ = data 
>>> shares 
50 
>>> price 
91.1 
>>>

但是请确保选择的变量名没有在其他地方用到过。

2. 从任意长度的可迭代对象中分解元素

(1) 问题

需要从某个可迭代对象中分解出N个元素,但是这个可迭代对象的长度可能超过N,这会导致出现“分解的值过多(too many values to unpack)”的异常。

(2) 解决方案

Python的“*表达式”可以用来解决这个问题。例如,假设开设了一门课程,并决定在期末的作业成绩中去掉第一个和最后一个,只对中间剩下的成绩做平均分统计。如果只有4个成绩,也许可以简单地将4个都分解出来,但是如果有24个呢?*表达式使这一切都变得简单:

def drop_first_last(grades): 
 first, *middle, last = grades 
 return avg(middle)

另一个用例是假设有一些用户记录,记录由姓名和电子邮件地址组成,后面跟着任意数量的电话号码。则可以像这样分解记录:

>>> record = ('Dave', 'dave@example.com', '773-555-1212', '847-555-1212') 
>>> name, email, *phone_numbers = user_record 
>>> name 
'Dave' 
>>> email 
'dave@example.com' 
>>> phone_numbers 
['773-555-1212', '847-555-1212'] 
>>>

不管需要分解出多少个电话号码(甚至没有电话号码),变量phone_numbers都一直是列表,而这是毫无意义的。如此一来,对于任何用到了变量phone_numbers的代码都不必对它可能不是一个列表的情况负责,或者额外做任何形式的类型检查。

由*修饰的变量也可以位于列表的第一个位置。例如,比方说用一系列的值来代表公司过去8个季度的销售额。如果想对最近一个季度的销售额同前7个季度的平均值做比较,可以这么做:

*trailing_qtrs, current_qtr = sales_record 
trailing_avg = sum(trailing_qtrs) / len(trailing_qtrs) 
return avg_comparison(trailing_avg, current_qtr)

从Python解释器的角度来看,这个操作是这样的:

>>> *trailing, current = [10, 8, 7, 1, 9, 5, 10, 3] 
>>> trailing 
[10, 8, 7, 1, 9, 5, 10] 
>>> current 
3

(3) 讨论

对于分解未知或任意长度的可迭代对象,这种扩展的分解操作可谓是量身定做的工具。通常,这类可迭代对象中会有一些已知的组件或模式(例如,元素1之后的所有内容都是电话号码),利用*表达式分解可迭代对象使得开发者能够轻松利用这些模式,而不必在可迭代对象中做复杂花哨的操作才能得到相关的元素。

*式的语法在迭代一个变长的元组序列时尤其有用。例如,假设有一个带标记的元组序列:

records = [ 
 ('foo', 1, 2), 
 ('bar', 'hello'), 
 ('foo', 3, 4), 
] 
def do_foo(x, y): 
 print('foo', x, y) 
def do_bar(s): 
 print('bar', s) 
for tag, *args in records: 
 if tag == 'foo': 
 do_foo(*args) 
elif tag == 'bar': 
 do_bar(*args)

当和某些特定的字符串处理操作相结合,比如做拆分(splitting)操作时,这种*式的语法所支持的分解操作也非常有用。例如:

>>> line = 'nobody:*:-2:-2:Unprivileged User:/var/empty:/usr/bin/false' 
>>> uname, *fields, homedir, sh = line.split(':') 
>>> uname 
'nobody' 
>>> homedir 
'/var/empty' 
>>> sh 
'/usr/bin/false' 
>>>

有时候可能想分解出某些值然后丢弃它们。在分解的时候,不能只是指定一个单独的*,但是可以使用几个常用来表示待丢弃值的变量名,比如_或者ign(ignored)。例如:

>>> record = ('ACME', 50, 123.45, (12, 18, 2012)) 
>>> name, *_, (*_, year) = record 
>>> name 
'ACME' 
>>> year 
2012 
>>>

*分解操作和各种函数式语言中的列表处理功能有着一定的相似性。例如,如果有一个列表,可以像下面这样轻松将其分解为头部和尾部:

>>> items = [1, 10, 7, 4, 5, 9] 
>>> head, *tail = items 
>>> head 
1 
>>> tail 
[10, 7, 4, 5, 9] 
>>>

在编写执行这类拆分功能的函数时,人们可以假设这是为了实现某种精巧的递归算法。例如:

>>> def sum(items): 
... head, *tail = items 
... return head + sum(tail) if tail else head 
... 
>>> sum(items) 
36 
>>>

但是请注意,递归真的不算是Python的强项,这是因为其内在的递归限制所致。因此,最后一个例子在实践中没太大的意义,只不过是一点学术上的好奇罢了。

3. 保存最后N个元素

(1) 问题

我们希望在迭代或是其他形式的处理过程中对最后几项记录做一个有限的历史记录统计。

(2) 解决方案

保存有限的历史记录可算是collections.deque的完美应用场景了。例如,下面的代码对一系列文本行做简单的文本匹配操作,当发现有匹配时就输出当前的匹配行以及最后检查过的N行文本。

from collections import deque 
def search(lines, pattern, history=5): 
 previous_lines = deque(maxlen=history) 
 for line in lines: 
 if pattern in line: 
 yield line, previous_lines 
 previous_lines.append(line) 
# Example use on a file 
if __name__ == '__main__': 
 with open('somefile.txt') as f: 
 for line, prevlines in search(f, 'python', 5): 
 for pline in prevlines: 
 print(pline, end='') 
 print(line, end='') 
 print('-'*20)

(3) 讨论

如同上面的代码片段中所做的一样,当编写搜索某项记录的代码时,通常会用到含有yield关键字的生成器函数。这将处理搜索过程的代码和使用搜索结果的代码成功解耦开来。如果对生成器还不熟悉,请参见4.3节。

deque(maxlen=N)创建了一个固定长度的队列。当有新记录加入而队列已满时会自动移除最老的那条记录。例如:

>>> q = deque(maxlen=3) 
>>> q.append(1) 
>>> q.append(2) 
>>> q.append(3) 
>>> q 
deque([1, 2, 3], maxlen=3) 
>>> q.append(4) 
>>> q 
deque([2, 3, 4], maxlen=3) 
>>> q.append(5) 
>>> q 
deque([3, 4, 5], maxlen=3)

尽管可以在列表上手动完成这样的操作(append、del),但队列这种解决方案要优雅得多,运行速度也快得多。

更普遍的是,当需要一个简单的队列结构时,deque可祝你一臂之力。如果不指定队列的大小,也就得到了一个无界限的队列,可以在两端执行添加和弹出操作,例如:

>>> q = deque() 
>>> q.append(1) 
>>> q.append(2) 
>>> q.append(3) 
>>> q 
deque([1, 2, 3]) 
>>> q.appendleft(4) 
>>> q 
deque([4, 1, 2, 3]) 
>>> q.pop() 
3 
>>> q 
deque([4, 1, 2]) 
>>> q.popleft() 
4

从队列两端添加或弹出元素的复杂度都是O(1)。这和列表不同,当从列表的头部插入或移除元素时,列表的复杂度为O(N)。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
python查询mysql中文乱码问题
Nov 09 Python
python dataframe 输出结果整行显示的方法
Jun 14 Python
用Pycharm实现鼠标滚轮控制字体大小的方法
Jan 15 Python
Python Excel处理库openpyxl使用详解
May 09 Python
一文秒懂python读写csv xml json文件各种骚操作
Jul 04 Python
PIL对上传到Django的图片进行处理并保存的实例
Aug 07 Python
python web框架 django wsgi原理解析
Aug 20 Python
python自动发微信监控报警
Sep 06 Python
详解python中docx库的安装过程
Nov 08 Python
使用pytorch实现可视化中间层的结果
Dec 30 Python
3分钟看懂Python后端必须知道的Django的信号机制
Jul 26 Python
python 实现Requests发送带cookies的请求
Feb 08 Python
python通过paramiko复制远程文件及文件目录到本地
Apr 30 #Python
python实现定时压缩指定文件夹发送邮件
Dec 22 #Python
python定时复制远程文件夹中所有文件
Apr 30 #Python
python实现图片转字符小工具
Apr 30 #Python
python 列表中[ ]中冒号‘:’的作用
Apr 30 #Python
python实现趣味图片字符化
Apr 30 #Python
python3对接mysql数据库实例详解
Apr 30 #Python
You might like
PHP 面向对象 PHP5 中的常量
2010/05/05 PHP
destoon实现首页显示供应、企业、资讯条数的方法
2014/07/15 PHP
PHP实现通用alert函数的方法
2015/03/11 PHP
laravel5.5安装jwt-auth 生成token令牌的示例
2019/10/24 PHP
关于Yii2框架跑脚本时内存泄漏问题的分析与解决
2019/12/01 PHP
在iframe里的页面编写js,实现在父窗口上创建动画效果展开和收缩的div(不变动iframe父窗口代码)
2011/12/20 Javascript
jquery修改属性值实例代码(设置属性值)
2014/01/06 Javascript
JQuery表格拖动调整列宽效果(自己动手写的)
2014/09/01 Javascript
jquery+CSS3模拟Path2.0动画菜单效果代码
2015/08/31 Javascript
javascript实现的网站访问量统计代码
2015/12/20 Javascript
用nodejs的实现原理和搭建服务器(动态)
2016/08/10 NodeJs
jQuery简单自定义图片轮播插件及用法示例
2016/11/21 Javascript
微信小程序 简单DEMO布局,逻辑,样式的练习
2016/11/30 Javascript
JS利用cookies设置每隔24小时弹出框
2017/04/20 Javascript
五步轻松实现zTree的使用
2017/11/01 Javascript
JS实现仿微信支付弹窗功能
2018/06/25 Javascript
javascript实现简单搜索功能
2020/03/26 Javascript
vue3.0 的 Composition API 的使用示例
2020/10/26 Javascript
[49:56]VG vs Optic 2018国际邀请赛小组赛BO2 第一场 8.19
2018/08/21 DOTA
python登陆asp网站页面的实现代码
2015/01/14 Python
python脚本设置超时机制系统时间的方法
2016/02/21 Python
Python简单遍历字典及删除元素的方法
2016/09/18 Python
Python正则表达式匹配中文用法示例
2017/01/17 Python
Python学习笔记之集合的概念和简单使用示例
2019/08/22 Python
pandas将多个dataframe以多个sheet的形式保存到一个excel文件中
2019/10/10 Python
Python实现分数序列求和
2020/02/25 Python
KARATOV珠宝在线商店:俄罗斯珠宝品牌
2019/03/13 全球购物
.NET初级开发工程师面试题(包括Javascript)
2012/08/22 面试题
技校生自我鉴定范文
2013/09/26 职场文书
企业安全生产目标责任书
2014/07/23 职场文书
中国在我心中演讲稿
2014/09/13 职场文书
大学生敬老院活动总结
2015/05/07 职场文书
二手房购房意向书
2015/05/09 职场文书
合作合同协议书
2016/03/21 职场文书
《弟子规》读后感:知廉耻、明是非、懂荣辱、辨善恶
2019/12/03 职场文书
「Manga Time Kirara MAX」2022年5月号封面公开
2022/03/21 日漫