百科狗-知识改变命运!
--

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

梵高1年前 (2023-11-21)阅读数 21#技术干货
文章标签分解

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(mostrecentcalllast):

File"",line1,in

ValueError:needmorethan2valuestounpack

>>>

(3)讨论

实际上不仅仅只是元组或列表,只要对象恰好是可迭代的,那么就可以执行分解操作。这包括字符串、文件、迭代器以及生成器。比如:

>>>s='Hello'

>>>a,b,c,d,e=s

>>>a

'H'

>>>b

'e'

>>>e

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

'o'

>>>

当做分解操作时,有时候可能想丢弃某些特定的值。Python并没有提供特殊的语法来实现这一点,但是通常可以选一个用不到的变量名,以此来作为要丢弃的值的名称。例如:

>>>data=['ACME',50,91.1,(2012,12,21)]

>>>_,shares,price,_=data

>>>shares

50

>>>price

91.1

>>>

但是请确保选择的变量名没有在其他地方用到过。

2.从任意长度的可迭代对象中分解元素

(1)问题

需要从某个可迭代对象中分解出N个元素,但是这个可迭代对象的长度可能超过N,这会导致出现“分解的值过多(toomanyvaluestounpack)”的异常。

(2)解决方案

Python的“*表达式”可以用来解决这个问题。例如,假设开设了一门课程,并决定在期末的作业成绩中去掉第一个和最后一个,只对中间剩下的成绩做平均分统计。如果只有4个成绩,也许可以简单地将4个都分解出来,但是如果有24个呢?*表达式使这一切都变得简单:

defdrop_first_last(grades):

first,*middle,last=grades

returnavg(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)

returnavg_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),

]

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)

当和某些特定的字符串处理操作相结合,比如做拆分(splitting)操作时,这种*式的语法所支持的分解操作也非常有用。例如:

>>>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'

>>>

有时候可能想分解出某些值然后丢弃它们。在分解的时候,不能只是指定一个单独的*,但是可以使用几个常用来表示待丢弃值的变量名,比如_或者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]

>>>

在编写执行这类拆分功能的函数时,人们可以假设这是为了实现某种精巧的递归算法。例如:

>>>defsum(items):

...head,*tail=items

...returnhead+sum(tail)iftailelsehead

...

>>>sum(items)

36

>>>

但是请注意,递归真的不算是Python的强项,这是因为其内在的递归限制所致。因此,最后一个例子在实践中没太大的意义,只不过是一点学术上的好奇罢了。

3.保存最后N个元素

(1)问题

我们希望在迭代或是其他形式的处理过程中对最后几项记录做一个有限的历史记录统计。

(2)解决方案

保存有限的历史记录可算是collections.deque的完美应用场景了。例如,下面的代码对一系列文本行做简单的文本匹配操作,当发现有匹配时就输出当前的匹配行以及最后检查过的N行文本。

fromcollectionsimportdeque

defsearch(lines,pattern,history=5):

previous_lines=deque(maxlen=history)

forlineinlines:

ifpatterninline:

yieldline,previous_lines

previous_lines.append(line)

#Exampleuseonafile

if__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)

>>>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编程中3个常用的数据结构和算法,希望对大家有所帮助,如果想要了解更多Python相关知识,请关注IT培训机构:开发教育。http://www.baikegou.com/

鹏仔微信 15129739599 鹏仔QQ344225443 鹏仔前端 pjxi.com 共享博客 sharedbk.com

免责声明:我们致力于保护作者版权,注重分享,当前被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理!邮箱:344225443@qq.com)

图片声明:本站部分配图来自网络。本站只作为美观性配图使用,无任何非法侵犯第三方意图,一切解释权归图片著作权方,本站不承担任何责任。如有恶意碰瓷者,必当奉陪到底严惩不贷!

内容声明:本文中引用的各种信息及资料(包括但不限于文字、数据、图表及超链接等)均来源于该信息及资料的相关主体(包括但不限于公司、媒体、协会等机构)的官方网站或公开发表的信息。部分内容参考包括:(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供参考使用,不准确地方联系删除处理!本站为非盈利性质站点,本着为中国教育事业出一份力,发布内容不收取任何费用也不接任何广告!)