本篇文章将介绍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个呢"htmlcode">
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)。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
免责声明:本站资源来自互联网收集,仅供用于学习和交流,请遵循相关法律法规,本站一切资源不代表本站立场,如有侵权、后门、不妥请联系本站删除!
稳了!魔兽国服回归的3条重磅消息!官宣时间再确认!
昨天有一位朋友在大神群里分享,自己亚服账号被封号之后居然弹出了国服的封号信息对话框。
这里面让他访问的是一个国服的战网网址,com.cn和后面的zh都非常明白地表明这就是国服战网。
而他在复制这个网址并且进行登录之后,确实是网易的网址,也就是我们熟悉的停服之后国服发布的暴雪游戏产品运营到期开放退款的说明。这是一件比较奇怪的事情,因为以前都没有出现这样的情况,现在突然提示跳转到国服战网的网址,是不是说明了简体中文客户端已经开始进行更新了呢?
更新日志
- 凤飞飞《我们的主题曲》飞跃制作[正版原抓WAV+CUE]
- 刘嘉亮《亮情歌2》[WAV+CUE][1G]
- 红馆40·谭咏麟《歌者恋歌浓情30年演唱会》3CD[低速原抓WAV+CUE][1.8G]
- 刘纬武《睡眠宝宝竖琴童谣 吉卜力工作室 白噪音安抚》[320K/MP3][193.25MB]
- 【轻音乐】曼托凡尼乐团《精选辑》2CD.1998[FLAC+CUE整轨]
- 邝美云《心中有爱》1989年香港DMIJP版1MTO东芝首版[WAV+CUE]
- 群星《情叹-发烧女声DSD》天籁女声发烧碟[WAV+CUE]
- 刘纬武《睡眠宝宝竖琴童谣 吉卜力工作室 白噪音安抚》[FLAC/分轨][748.03MB]
- 理想混蛋《Origin Sessions》[320K/MP3][37.47MB]
- 公馆青少年《我其实一点都不酷》[320K/MP3][78.78MB]
- 群星《情叹-发烧男声DSD》最值得珍藏的完美男声[WAV+CUE]
- 群星《国韵飘香·贵妃醉酒HQCD黑胶王》2CD[WAV]
- 卫兰《DAUGHTER》【低速原抓WAV+CUE】
- 公馆青少年《我其实一点都不酷》[FLAC/分轨][398.22MB]
- ZWEI《迟暮的花 (Explicit)》[320K/MP3][57.16MB]