当前位置: 首页 > news >正文

Python Cookbook-5.12 检查序列的成员

任务

你需要对一个列表执行很频繁的成员资格检査。而in操作符的 O(n)时间复杂度对性能的影响很大,你也不能将序列转化为一个字典或者集合,因为你还需要保留原序列的元素顺序。

解决方案

假设需要给列表添加一个在该列表中不存在的元素。一个可行的方法是写这样一个函数:

def addUnique(baseList,otherList):
	auxDict = dict.fromkeys(baseList)
	for item in otherList:
		if item not in auxDict:
			baseList.append(item)
			auxDict[item] = None

如果你的代码只是在 Python 2.4下运行,那么将辅助字典换成辅助集合效果是完全一样的。

讨论

下面给出一个简单(天真?)的方式,看上去相当不错:

def addUnique_simple(baselist,otherList):
	for item in otherList:
		if item not in baseList:
			baseList.append(item)

如果列表很短的话,这个方法倒也没问题。

但是,如果列表不是很短,这个简单的方法会非常慢。当你用if item not in baseList 这样的代码进行检查时,Python只会用一种方式执行in操作:对列表 baselist 的元素进行内部的循环遍历,如果找到一个元素等于item 则返回 True,如果直到循环结束也没有发现相等的元素则返回 False。in 操作的平均执行时间是正比于1en(baseList)的。addUnique simple 执行了len(otherList)次 in操作,因此它消耗的时间正比于这两个列表长度的乘积。

而解决方案给出的 addUnique 函数,首先创建了一个辅助的字典 auxDict,这一步的时间正比于len(baseList)。然后在循环中检査 dict 的成员——这是造成巨大差异的一步,因为检查一个元素是否处于一个dict 中的时间大致是一个常数,而与 dict 中元素的数目没有关系。因此,那个for循环消耗的时间正比于len(otherList),这样,整个函数所需要的时间就正比于这两个列表的长度之和。

对于运行时间的分析还可以挖得更深一点,因为在 addUnique_simple 中 baseList 的长度并不是一个常量,每当找到一个不属于 baseList的元素,baseList的长度就会增加。但这样的分析结果不会与前面的简化版的结果有太大出入。我们可以准备一些用例进行测试。当每个列表中有10个整数且有50%的重叠时,简化版比解决方案给出的函数慢30%,这样的性能下降还可以忽略。若每个列表都有100个整数,而且仍然有 50%的重叠部分,简化版比解决方案的函数慢12倍–这种级别的减速效果就无法忽略了,而且当列表变得更长的时候,情况也变得更糟。

有时,将一个辅助的 dict和序列一起使用并封装成一个对象能提高你的应用程序的性能。但在这个例子中,必须在序列被修改时不断地维护 dict,以保证它总是和序列当前所拥有的元素保持同步。这个维护任务并不是很简单,我们有很多方法来实现同步。下面给出一种“即时”的同步方式,当需要检查某元素,或者字典的内容可能已经无法和列表内容保持同步时,我们就重新构建一个辅助 dict。由于开销很小,下面的类优化了index方法和成员检查部分的代码:

class list_with_aux_dict(list):
	def __init__(self,iterable = ()):
		list.__init__(self,iterable)
		self._dict_ok = False
	def _rebuild_dict(self):
		self.dict = {}
		for i,item in enumerate(self):
			if item not in self._dict:
				self._dict[item] = i
		self._dict_ok = True
	def __contains__(self,item):
		if not self._dict_ok:
			self._rebuild_dict()
		return item in self._dict
	def index(self,item):
		if not self._dict_ok:
			self._rebuild_dict()
		try: return self,_dict[item]
		except KeyError:raise ValueError
def _wrapMutatorMethod(methname):
	_method = getattr(list,methname)
	def wrapper(self,*args):
		#重置字典的OK标志,然后委托给真正的方法
		self._dict_ok = False
		return method(self,*args)
	#只适用于Python 2.4:wrapper.__name__ = _method.__name__
	setattr(list_with_aux_dict,methname,wrapper)
	for meth in 'setitem delitem setslice delslice iadd'.split():
		_wrapMutatorMethod('__%s__'%meth)
		for meth in 'append insert pop remove extend'.split():
			_wrapMutatorMethod(meth)
		del _wrapMethod#删除辅助函数,已经不再需要它了

list_with_aux_dict扩展了list,并将原 list的所有方法仍然委托给它,除了__contains__和 index。所有能够修改 list 的方法都被封装进了一个闭包,该闭包负责重置一个标志以确保辅助字典的有效性。Python的in操作符调用__contains__方法。除非标志被设置,否则 list_with_aux_dict的__contains__方法会重建辅助字典(标志被设置时,重建没有必要),而index方法仍然像原先一样工作。

上述 list_with_aux_dict 类并没有用帮助函数为列表的所有属性方法绑定和安装一个闭包,而是只取所需,我们也可以在 list_with_aux_dict 的主体中写出所有的 def语句来替代 wrapper 方法。但是上述代码有个重要的优点是消除了几余和重复(重复和啰嗦的代码让人生厌,而且容易滋生 bug)。Python 在自省和动态改变方面的能力给你提供了一个选择:可以创建一个 wrapper方法,用一种聪明而简练的方式,或者,如果你想避免使用被人称为黑魔法的类对象的自省和动态改变,也可以写一堆重复嗦的代码。

list_with_aux_dict 的结构很适合通常的使用模式,即对序列的修改操作一般总是集中出现,然后接着又会有一段时间序列无须被修改,但需要检查元素的成员资格。如果参数 baseList 不是一个普通的列表,而是list_with_aux_dict 的一个实例,早先展示的addUnique_simple 函数也不会因此得到任何性能上的提升,因为这个函数会交替地进行成员资格检查和序列修改。因此,类list_with_aux_dict 中过多的辅助字典的重建影响了函数的性能。(除非是针对某个特例,比如oterList 中绝大多数元素都已经在 baseList中出现过了,因此对序列的修改相比于对元素的检查,发生的次数要少得多。)

对这些成员资格的检查所做的优化有个重要的前提,即序列中的值必须是可哈希的(不然的话,它们不能被用来做字典的键或者集合的元素)。举个例子,元组的列表仍适用于本节的解决方案,但对于列表的列表,我们恐怕得另外想办法了。

http://www.dtcms.com/a/123379.html

相关文章:

  • DAY06:【pytorch】图像增强
  • day29-贪心__134. 加油站__135. 分发糖果__860.柠檬水找零__406.根据身高重建队列
  • 三分钟学会使用java RandomAccessFile随机读写IO
  • 数字内容体验的技术支持包含什么?
  • 公司内部建立apt源
  • Animated Movement Color
  • 【书籍】DeepSeek谈《持续交付2.0》
  • 第5篇:Linux程序访问控制FPGA端LEDR<三>
  • 如何用 nvm alias default 18.20.8 实现全局 Node.js 版本管理?一篇保姆级指南!!!
  • 深入解析回环检测:从原理到C++实战
  • 批量清空图片的相机参数、地理位置等敏感元数据
  • 电商素材革命:影刀RPA魔法指令3.0驱动批量去水印,实现秒级素材净化
  • 【C++】右值引用、移动语义与完美转发
  • 【倍增】P10264 [GESP202403 八级] 接竹竿|普及+
  • java继承练习
  • 走多远(拓扑排序,dp)
  • ChatRoom测试报告
  • 手眼标定-眼在手上
  • 安卓设备配置PAC代理服务器的完整指南:实现智能分流与开发加速
  • OfficePlus去掉PDF文件右键菜单里的PDF转换
  • Java Map和Set集合应用
  • docker安装nginx,基础命令,目录结构,配置文件结构
  • 0410 | 软考高项笔记:项目管理概述
  • jQuery多库共存
  • 甘特图和里程碑趋势图在项目监控中有哪些实际应用?
  • 本地laravel项目【dcat-admin】部署到liunx服务器
  • 论文笔记:Dynamic Spectral Graph Anomaly Detection
  • 先占个日常,等会写。
  • 如何白嫖Grok3 API? 如何使用Grok3 API调用实例?怎么使用Grok3模型?
  • 从零实现Agent智能体配置使用(Ragflow)