Python内置了很多非常有用的数据结构,比如列表(lists)、集合(sets)和字典(dictionary)。对于绝大多数情况,我们可以直接使用这些数据结构。然而,我们往往还需要考虑搜索、排序、排列和过滤等常见问题。本文将介绍3种常见的数据结构和与数据相关的算法。此外,collections模块中还包含了针对各种数据结构的解决方案。1.将一个序列分解成单独的变量(1)问题我们有一个包含N个元素的元组或序列,现在想将它分解成N个单独的变量。(2)解决方案任何序列(或可迭代对象)都可以通过简单的赋值操作分解为单个变量。一个要求是变量的总数和结构符合序列。例如:>>>p=(4,5)>>>x,y=p>>>x4>>>y5>>>>>>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'>>>year2012>>>mon12>>>day21>>>如果元素个数不匹配,会报错。例如:>>>p=(4,5)>>>x,y,z=pTraceback(mostrecentcalllast):File"",line1,inValueError:needmorethan2valuestounpack>>>(3)讨论实际不只是元组或列表,只要对象恰好是可迭代的,那么就可以进行分解操作。这包括字符串、文件、迭代器和生成器。例如:>>>s='Hello'>>>a,b,c,d,e=s>>>a'H'>>>b'e'>>>e'o'>>>asdecomposition操作时,有时你可能希望丢弃某些值。Python没有为此提供特殊的语法,但您通常可以选择一个未使用的变量名作为要丢弃的值的名称。例如:>>>data=['ACME',50,91.1,(2012,12,21)]>>>_,shares,price,_=data>>>shares50>>>price91.1>>>但请确保您选择的变量名未在别处使用。2.从一个任意长度的可迭代对象中分解元素(1)该题需要从一个可迭代对象中分解出N个元素,但是这个可迭代对象的长度可能会超过N,这会导致“分解后的值太大很多(太多值无法解包)”异常。(2)解决方案Python的“*表达式”可以解决这个问题。例如,假设提供一门课程,并决定在学期结束时降低第一个和最后一个作业的成绩,并且只在中间对剩余的成绩进行平均。如果只有4个等级,也许你可以简单地分解出所有4个,但如果有24个呢?*表达式使这变得简单:defdrop_first_last(grades):first,*middle,last=gradesreturnavg(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']>>>无论多少numbers需要分解电话号码(甚至不是电话号码),变量phone_numbers始终是一个列表,这是没有意义的。这样,任何使用变量phone_numbers的代码都不必对它可能不是列表这一事实负责,也不必进行任何类型的额外类型检查。由*修饰的变量也可以在列表的第一个位置。例如,假设有一系列值代表一家公司过去8个季度的销售额。如果要将最近一个季度的销售额与前7个季度的平均值进行比较,可以这样做:*trailing_qtrs,current_qtr=sales_recordtrailing_avg=sum(trailing_qtrs)/len(trailing_qtrs)从Python解释器的角度来看,这个操作看起来像这样:>>>*trailing,current=[10,8,7,1,9,5,10,3]>>>trailing[10,8,7,1,9,5,10]>>>current3(3)讨论这个扩展的分解操作是一个专门用于分解未知或任意长度的可迭代对象的工具。通常,这种可迭代对象中会有一些已知的组件或模式(例如,元素1之后的所有内容都是电话号码),使用*表达式分解可迭代对象可以让开发人员轻松使用这些模式,而你不需要不必对可迭代对象执行复杂的花哨操作来获取相关元素。*风格的语法在遍历可变长度的元组序列时特别有用。例如,假设有一个标记元组序列:records=[('foo',1,2),('bar','hello'),('foo',3,4),]defdo_foo(x,y):print('foo',x,y)defdo_bar(s):print('bar',s)fortag,*argsinrecords:iftag=='foo':do_foo(*args)eliftag=='bar':do_bar(*args)当结合一些特定的字符串处理操作,比如拆分操作时,这种*-style语法支持的分解操作也非常有用。例如:>>>line='nobody:*:-2:-2:UnprivilegedUser:/var/empty:/usr/bin/false'>>>uname,*fields,homedir,sh=line.split(':')>>>uname'nobody'>>>homedir'/var/empty'>>>sh'/usr/bin/false'>>>有时你可能想分解出某些值并丢弃它们。分解时不能只指定一个*,可以使用几个常用的变量名来表示要丢弃的值,比如_或ignored(忽略)。例如:>>>record=('ACME',50,123.45,(12,18,2012))>>>name,*_,(*_,year)=record>>>name'ACME'>>>year2012>>>*分解操作与各种函数式语言中的列表处理函数有一定的相似性。例如,如果您有一个列表,您可以像这样轻松地将它分解为头部和尾部:>>>items=[1,10,7,4,5,9]>>>head,*tail=items>>>head1>>>tail[10,7,4,5,9]>>>在编写执行此类拆分的函数时,可以假设这是为了实现某种简洁的递归算法。例如:>>>defsum(items):...head,*tail=items...returnhead+sum(tail)iftailelsehead...>>>sum(items)36>>>但是注意递归确实不是确实是Python的强项,这是由于其固有的递归限制。所以最后一个例子在实践中没有多大意义,只是有点学术好奇。3.保存最后N个元素(1)问题我们想在迭代或其他形式的处理过程中,对最后几条记录做一个有限的历史记录统计。(2)解决方案保存有限的历史记录可以看作collections.deque的应用场景。例如,下面的代码对一系列文本行进行简单的文本匹配操作,当匹配到时,输出当前匹配行和最后检查的N行文本。fromcollectionsimportdequedefsearch(lines,pattern,history=5):previous_lines=deque(maxlen=history)forlineinlines:ifpatterninline:yieldline,previous_linesprevious_lines.append(line)#Exampleuseonafileif__name__=='__main__':withopen('somefile.txt')asf:forline,prevlinesinsearch(f,'python',5):forplineinprevlines: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)>>>qdeque([1,2,3],maxlen=3)>>>q.append(4)>>>qdeque([2,3,4],maxlen=3)>>>q.append(5)>>>qdeque([3,4,5],maxlen=3)虽然可以在列表上手动完成此类操作(追加、删除),但队列是一种更优雅的解决方案并且运行速度更快。更一般地说,当需要一个简单的队列结构时,双端队列会派上用场。如果不指定队列的大小,则得到一个无界队列,可以在两端进行add和pop操作,例如:>>>q=deque()>>>q.append(1)>>>q.append(2)>>>q.append(3)>>>qdeque([1,2,3])>>>q.appendleft(4)>>>qdeque([4,1,2,3])>>>q.pop()3>>>qdeque([4,1,2])>>>q.popleft()4从队列两端添加或弹出元素的复杂度为O(1).这与列表不同,后者在从列表头部插入或删除元素时具有O(N)的复杂度。