浅谈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代理抓取并验证使用多线程实现
May 03 Python
python获取图片颜色信息的方法
Mar 18 Python
Python实现列表删除重复元素的三种常用方法分析
Nov 24 Python
几种实用的pythonic语法实例代码
Feb 24 Python
python根据url地址下载小文件的实例
Dec 18 Python
使用python opencv对目录下图片进行去重的方法
Jan 12 Python
Django REST framework内置路由用法
Jul 26 Python
Python二元赋值实用技巧解析
Oct 25 Python
Python中顺序表原理与实现方法详解
Dec 03 Python
使用Keras预训练模型ResNet50进行图像分类方式
May 23 Python
使用Python爬虫爬取小红书完完整整的全过程
Jan 19 Python
Python实现socket库网络通信套接字
Jun 04 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
ubuntu10.04配置 nginx+php-fpm模式的详解
2013/06/03 PHP
JS仿淘宝实现的简单滑动门效果代码
2015/10/14 Javascript
jquery制作属于自己的select自定义样式
2015/11/23 Javascript
学习JavaScript设计模式(链式调用)
2015/11/26 Javascript
javascript如何定义对象数组
2016/06/07 Javascript
JS弹出窗口插件zDialog简单用法示例
2016/06/12 Javascript
基于JS实现回到页面顶部的五种写法(从实现到增强)
2016/09/03 Javascript
JavaScript实现翻页功能(附效果图)
2017/02/16 Javascript
解决vue-quill-editor上传内容由于图片是base64的导致字符太长的问题
2018/08/20 Javascript
[08:07]DOTA2每周TOP10 精彩击杀集锦vol.8
2014/06/25 DOTA
解析Python中的eval()、exec()及其相关函数
2017/12/20 Python
python中format()函数的简单使用教程
2018/03/14 Python
python如何把嵌套列表转变成普通列表
2018/03/20 Python
Python利用递归实现文件的复制方法
2018/10/27 Python
PyTorch基本数据类型(一)
2019/05/22 Python
python点击鼠标获取坐标(Graphics)
2019/08/10 Python
python实现机器人卡牌
2019/10/06 Python
python二分法查找算法实现方法【递归与非递归】
2019/12/06 Python
PyTorch中topk函数的用法详解
2020/01/02 Python
Pycharm 2020年最新激活码(亲测有效)
2020/09/18 Python
Python 写了个新型冠状病毒疫情传播模拟程序
2020/02/14 Python
Python3爬虫中Selenium的用法详解
2020/07/10 Python
日本无添加化妆品:HABA
2016/08/18 全球购物
简约控的天堂:The Undone
2016/12/21 全球购物
定义一结构体数组表示分数,并求两个分数相加之和
2013/06/11 面试题
市场营销专业个人自荐信格式
2013/09/21 职场文书
应届行政管理专业个人自我评价
2013/12/28 职场文书
《灯光》教学反思
2014/02/08 职场文书
年终晚会活动方案
2014/08/21 职场文书
医院党的群众路线教育实践活动领导班子对照检查材料
2014/09/25 职场文书
2014年内部审计工作总结
2014/12/09 职场文书
司机岗位职责
2015/02/04 职场文书
2015年市场营销工作总结
2015/07/23 职场文书
劳动模范获奖感言
2015/07/31 职场文书
离婚协议书格式范本
2016/03/18 职场文书
详细聊聊MySQL中慢SQL优化的方向
2021/08/30 MySQL