浅谈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 相关文章推荐
教你如何在Django 1.6中正确使用 Signal
Jun 22 Python
pygame学习笔记(4):声音控制
Apr 15 Python
简单讲解Python中的数字类型及基本的数学计算
Mar 11 Python
python自带的http模块详解
Nov 06 Python
人机交互程序 python实现人机对话
Nov 14 Python
python matplotlib实现双Y轴的实例
Feb 12 Python
对Python获取屏幕截图的4种方法详解
Aug 27 Python
多个python文件调用logging模块报错误
Feb 12 Python
IntelliJ 中配置 Anaconda的过程图解
Jun 01 Python
Python logging日志模块 配置文件方式
Jul 12 Python
关于django python manage.py startapp 应用名出错异常原因解析
Dec 15 Python
Python3利用openpyxl读写Excel文件的方法实例
Feb 03 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中数据的批量导入(csv文件)
2006/10/09 PHP
PHP和XSS跨站攻击的防范
2007/04/17 PHP
PHP5中Cookie与 Session使用详解
2013/04/30 PHP
PHP PDO fetch 模式各种参数的输出结果一览
2015/01/07 PHP
修改WordPress中文章编辑器的样式的方法详解
2015/12/15 PHP
PHP实现的下载远程文件类定义与用法示例
2017/07/05 PHP
IE/FireFox具备兼容性的拖动代码
2007/08/13 Javascript
IE innerHTML,outerHTML所引起的问题
2009/06/04 Javascript
DLL+ ActiveX控件+WEB页面调用例子
2010/08/07 Javascript
PHP 与 js的通信(via ajax,json)
2010/11/16 Javascript
javascript自动给文本url地址增加链接的方法分享
2014/01/20 Javascript
js函数在frame中的相互调用详解
2014/03/03 Javascript
javascript 获取元素样式必杀技
2014/05/04 Javascript
javascript面向对象之共享成员属性与方法及prototype关键字用法
2015/01/13 Javascript
jQuery网页版打砖块小游戏源码分享
2015/08/20 Javascript
jquery计算出left和top,让一个div水平垂直居中的简单实例
2016/07/13 Javascript
JSP防止网页刷新重复提交数据的几种方法
2016/11/19 Javascript
js时间戳格式化成日期格式的多种方法介绍
2017/02/16 Javascript
JS 组件系列之BootstrapTable的treegrid功能
2017/06/16 Javascript
解决bootstrap模态框数据缓存的问题方法
2018/08/10 Javascript
Python 命令行非阻塞输入的小例子
2013/09/27 Python
Python合并多个装饰器小技巧
2015/04/28 Python
Python 列表(List) 的三种遍历方法实例 详解
2017/04/15 Python
Python Gluon参数和模块命名操作教程
2019/12/18 Python
基于python实现获取网页图片过程解析
2020/05/11 Python
整理HTML5移动端开发的常用触摸事件
2016/04/15 HTML / CSS
canvas实现漂亮的下雨效果的示例
2018/04/18 HTML / CSS
新加坡网上美容店:Hermo新加坡
2019/06/19 全球购物
主题酒店策划书
2014/01/28 职场文书
政府领导干部个人对照检查材料思想汇报
2014/09/24 职场文书
2014年幼儿园工作总结
2014/11/10 职场文书
五四青年节活动总结
2015/02/10 职场文书
2015小学教师年度工作总结
2015/05/12 职场文书
学习nginx基础知识
2021/09/04 Servers
Python爬虫入门案例之回车桌面壁纸网美女图片采集
2021/10/16 Python
MySQL中dd::columns表结构转table过程及应用详解
2022/09/23 MySQL