侯体宗的博客
  • 首页
  • Hyperf版
  • beego仿版
  • 人生(杂谈)
  • 技术
  • 关于我
  • 更多分类
    • 文件下载
    • 文字修仙
    • 中国象棋ai
    • 群聊
    • 九宫格抽奖
    • 拼图
    • 消消乐
    • 相册

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

Python  /  管理员 发布于 7年前   138

本篇文章将介绍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', '[email protected]', '773-555-1212', '847-555-1212') >>> name, email, *phone_numbers = user_record >>> name 'Dave' >>> email '[email protected]' >>> 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)。

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


  • 上一条:
    OpenCV-Python 摄像头实时检测人脸代码实例
    下一条:
    python通过paramiko复制远程文件及文件目录到本地
  • 昵称:

    邮箱:

    0条评论 (评论内容有缓存机制,请悉知!)
    最新最热
    • 分类目录
    • 人生(杂谈)
    • 技术
    • linux
    • Java
    • php
    • 框架(架构)
    • 前端
    • ThinkPHP
    • 数据库
    • 微信(小程序)
    • Laravel
    • Redis
    • Docker
    • Go
    • swoole
    • Windows
    • Python
    • 苹果(mac/ios)
    • 相关文章
    • 在python语言中Flask框架的学习及简单功能示例(0个评论)
    • 在Python语言中实现GUI全屏倒计时代码示例(0个评论)
    • Python + zipfile库实现zip文件解压自动化脚本示例(0个评论)
    • python爬虫BeautifulSoup快速抓取网站图片(1个评论)
    • vscode 配置 python3开发环境的方法(0个评论)
    • 近期文章
    • 在windows10中升级go版本至1.24后LiteIDE的Ctrl+左击无法跳转问题解决方案(0个评论)
    • 智能合约Solidity学习CryptoZombie第四课:僵尸作战系统(0个评论)
    • 智能合约Solidity学习CryptoZombie第三课:组建僵尸军队(高级Solidity理论)(0个评论)
    • 智能合约Solidity学习CryptoZombie第二课:让你的僵尸猎食(0个评论)
    • 智能合约Solidity学习CryptoZombie第一课:生成一只你的僵尸(0个评论)
    • 在go中实现一个常用的先进先出的缓存淘汰算法示例代码(0个评论)
    • 在go+gin中使用"github.com/skip2/go-qrcode"实现url转二维码功能(0个评论)
    • 在go语言中使用api.geonames.org接口实现根据国际邮政编码获取地址信息功能(1个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf分页文件功能(0个评论)
    • gmail发邮件报错:534 5.7.9 Application-specific password required...解决方案(0个评论)
    • 近期评论
    • 122 在

      学历:一种延缓就业设计,生活需求下的权衡之选中评论 工作几年后,报名考研了,到现在还没认真学习备考,迷茫中。作为一名北漂互联网打工人..
    • 123 在

      Clash for Windows作者删库跑路了,github已404中评论 按理说只要你在国内,所有的流量进出都在监控范围内,不管你怎么隐藏也没用,想搞你分..
    • 原梓番博客 在

      在Laravel框架中使用模型Model分表最简单的方法中评论 好久好久都没看友情链接申请了,今天刚看,已经添加。..
    • 博主 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 @1111老铁这个不行了,可以看看近期评论的其他文章..
    • 1111 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 网站不能打开,博主百忙中能否发个APP下载链接,佛跳墙或极光..
    • 2016-10
    • 2016-11
    • 2018-04
    • 2020-03
    • 2020-04
    • 2020-05
    • 2020-06
    • 2022-01
    • 2023-07
    • 2023-10
    Top

    Copyright·© 2019 侯体宗版权所有· 粤ICP备20027696号 PHP交流群

    侯体宗的博客