浅谈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深入学习之内存管理
Aug 31 Python
Python中使用SAX解析xml实例
Nov 21 Python
python实现字符串连接的三种方法及其效率、适用场景详解
Jan 13 Python
关于Python中Inf与Nan的判断问题详解
Feb 08 Python
对numpy中的transpose和swapaxes函数详解
Aug 02 Python
使用python获取电脑的磁盘信息方法
Nov 01 Python
python机器学习库scikit-learn:SVR的基本应用
Jun 26 Python
python数据类型可变不可变知识点总结
Mar 06 Python
在主流系统之上安装Pygame的方法
May 20 Python
Jmeter HTTPS接口测试证书导入过程图解
Jul 22 Python
matplotlib图例legend语法及设置的方法
Jul 28 Python
在NumPy中深拷贝和浅拷贝相关操作的定义和背后的原理
Apr 14 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中4个加速、缓存扩展的区别和选用建议
2014/03/12 PHP
destoon实现会员商铺中指定会员或会员组投放广告的方法
2014/08/21 PHP
php实现通过ftp上传文件
2015/06/19 PHP
thinkphp3.x中session方法的用法分析
2016/05/20 PHP
Laravel框架执行原生SQL语句及使用paginate分页的方法
2018/08/17 PHP
javascript之大字符串的连接的StringBuffer 类
2007/05/08 Javascript
javaScript 数值型和字符串型之间的转换
2009/07/25 Javascript
jQuery的显示和隐藏方法与css隐藏的样式对比
2013/10/18 Javascript
在浏览器中实现图片粘贴的jQuery插件-- pasteimg使用指南
2014/12/29 Javascript
JQuery中attr属性和jQuery.data()学习笔记【必看】
2016/05/18 Javascript
JS实用的带停顿的逐行文本循环滚动效果实例
2016/11/23 Javascript
js实现股票实时刷新数据案例
2017/05/14 Javascript
Nodejs+angularjs结合multiparty实现多图片上传的示例代码
2017/09/29 NodeJs
JavaScript引用类型之基本包装类型实例分析【Boolean、Number和String】
2018/08/09 Javascript
微信小程序调用天气接口并且渲染在页面过程详解
2019/06/24 Javascript
JS+canvas五子棋人机对战实现步骤详解
2020/06/04 Javascript
利用vue3+ts实现管理后台(增删改查)
2020/10/30 Javascript
详解node.js创建一个web服务器(Server)的详细步骤
2021/01/15 Javascript
python中zip和unzip数据的方法
2015/05/27 Python
12步教你理解Python装饰器
2016/02/25 Python
Python装饰器用法示例小结
2018/02/11 Python
numpy.linspace函数具体使用详解
2019/05/27 Python
纯python进行矩阵的相乘运算的方法示例
2019/07/17 Python
使用 python pyautogui实现鼠标键盘控制功能
2019/08/04 Python
scikit-learn线性回归,多元回归,多项式回归的实现
2019/08/29 Python
python每5分钟从kafka中提取数据的例子
2019/12/23 Python
Django 解决model 反向引用中的related_name问题
2020/05/19 Python
HTML5 Canvas API中drawImage()方法的使用实例
2016/03/25 HTML / CSS
世界上最大的折扣香水店:FragranceNet.com
2016/10/26 全球购物
18-35岁旅游团的全球领导者:Contiki
2017/02/08 全球购物
英国最大的海报商店:GB Posters
2018/03/20 全球购物
五好家庭事迹材料
2014/12/20 职场文书
大学生自荐材料范文
2014/12/30 职场文书
离职信范本
2015/06/23 职场文书
MySQL数字类型自增的坑
2021/05/07 MySQL
解决Oracle数据库用户密码过期
2022/05/11 Oracle