Python中如何高效统计两个关联列表中得票最高的姓名
问题描述
现有两个一一对应的关联列表:
names = ['alan_grant', 'alan_grant', 'alan_grant', 'alan_grant', 'alan_grant', 'claire_dearing', 'claire_dearing', 'claire_dearing', 'claire_dearing', 'claire_dearing', 'ellie_sattler', 'ellie_sattler', 'ellie_sattler', 'ellie_sattler', 'ellie_sattler', 'ian_malcolm', 'ian_malcolm', 'ian_malcolm', 'ian_malcolm', 'ian_malcolm', 'john_hammond', 'john_hammond', 'john_hammond', 'john_hammond', 'john_hammond', 'owen_grady', 'owen_grady', 'owen_grady', 'owen_grady', 'owen_grady'] votes = [True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, True, True, True, True]
votes列表是面部识别算法对names列表对应位置项的匹配结果,需求为关联所有取值为True的匹配结果和对应姓名,统计出现频次最高的姓名作为最终胜出结果。
目前已实现两种可得到正确结果owen_grady的方案:
- 方案1:原生字典遍历实现
characters = {} for name, vote in list(zip(names, votes)): if vote == True: characters[name] = characters.get(name, 0) + 1 #print(characters) print(max(characters, key=characters.get))
- 方案2:列表推导式结合
collections.Counter实现
from collections import Counter characters = [name for name, vote in list(zip(names, votes)) if vote == True] #print(characters) print(Counter(characters).most_common()[0][0])
待解答问题:
- 上述两种实现哪一种效率更高?
- 是否存在效率更优的实现方式,要求最终仅输出结果
owen_grady即可。
回答
两种现有方案的效率对比
小数据量场景下两者差异可以忽略,数据量较大时原生字典遍历的实现效率更高,原因如下:
- 方案2的列表推导式会先生成一个仅包含投票为
True的姓名的中间列表,额外占用内存的同时,还多了一次遍历中间列表构建Counter的开销;方案1是单次遍历zip结果,边遍历边计数,没有中间列表的额外开销。 - 两个现有实现都存在一个无意义的性能损耗:给
zip返回的可迭代对象套了list(),zip本身返回的迭代器可以直接用于遍历,强制转列表会额外生成一组元组,白白增加内存占用和耗时,去掉后两个方案的运行速度还能进一步提升。
更优实现方案
如果追求极致效率,可以采用单次遍历+边计数边维护当前最大值的写法,省去最后遍历计数字典找最大值的步骤,也不需要构建任何中间容器:
counts = {} max_name = None max_cnt = 0 for name, is_match in zip(names, votes): if not is_match: continue current = counts.get(name, 0) + 1 counts[name] = current if current > max_cnt: max_cnt = current max_name = name print(max_name)
如果希望代码更简洁,同时兼顾效率,可以用C实现的itertools.compress替代Python层的列表推导式筛选,省去中间列表开销:
from collections import Counter from itertools import compress print(Counter(compress(names, votes)).most_common(1)[0][0])
给most_common传入参数1可以让Counter内部不需要全量排序,只取Top1元素,比不传参的写法更快。
性能参考
在给出的30条测试数据下,各版本耗时差在微秒级,完全感知不到;当数据量提升到百万级时:
- 边遍历边维护最大值的原生实现,比原方案2快30%左右
compress+Counter的简洁实现,比原方案2快20%左右- 两种优化方案的逻辑和原方案一致,出现多姓名同票最高的情况时,会返回最先达到最高票数的姓名,和原有逻辑兼容。
内容的提问来源于stack exchange,提问作者blackraven
相关产品推荐
相关产品推荐

