在 Python 列表中查找最常见元素的有效方法是什么?
我的列表项可能无法散列,因此无法使用字典。此外,在绘制的情况下,应返回索引最低的项目。例子:
>>> most_common(['duck', 'duck', 'goose']) 'duck' >>> most_common(['goose', 'duck', 'duck', 'goose']) 'goose'
提出了这么多解决方案,我很惊讶没有人提出我认为显而易见的解决方案(对于不可散列但可比较的元素)——[ itertools.groupby][1]。 itertools提供快速、可重用的功能,并允许您将一些棘手的逻辑委托给经过良好测试的标准库组件。考虑例如:
itertools.groupby
itertools
import itertools import operator def most_common(L): # get an iterable of (item, iterable) pairs SL = sorted((x, i) for i, x in enumerate(L)) # print 'SL:', SL groups = itertools.groupby(SL, key=operator.itemgetter(0)) # auxiliary function to get "quality" for an item def _auxfun(g): item, iterable = g count = 0 min_index = len(L) for _, where in iterable: count += 1 min_index = min(min_index, where) # print 'item %r, count %r, minind %r' % (item, count, min_index) return count, -min_index # pick the highest-count/earliest item return max(groups, key=_auxfun)[0]
当然,这可以写得更简洁,但我的目标是最大限度地清晰。这两个print语句可以取消注释,以便更好地了解运行中的机制;例如, 打印 未注释:
print
print most_common(['goose', 'duck', 'duck', 'goose'])
发出:
SL: [('duck', 1), ('duck', 2), ('goose', 0), ('goose', 3)] item 'duck', count 2, minind 1 item 'goose', count 2, minind 0 goose
如您所见,SL是一个对列表,每对一个项目后跟项目在原始列表中的索引(实现关键条件,即如果具有相同最高计数的“最常见”项目> 1,则结果必须是最早出现的)。
SL
groupby仅按项目分组(通过operator.itemgetter)。辅助函数,在计算期间每个分组调用一次max,接收并在内部解包一个组 - 一个包含两个项目的元组,(item, iterable)其中可迭代的项目也是两个项目的元组,(item, original index)[[the items of SL]]。
groupby
operator.itemgetter
max
(item, iterable)
(item, original index)
然后辅助函数使用循环来确定组的可迭代项中的条目数 和 最小原始索引;它将这些作为组合的“质量键”返回,最小索引符号已更改,因此max操作将“更好地”考虑原始列表中较早出现的那些项目。
如果这段代码不太 担心时间和空间上的大 O 问题,它可能会简单得多,例如......:
def most_common(L): groups = itertools.groupby(sorted(L)) def _auxfun((item, iterable)): return len(list(iterable)), -L.index(item) return max(groups, key=_auxfun)[0]
相同的基本思想,只是表达得更简单和紧凑......但是,唉,额外的 O(N) 辅助空间(将组的可迭代项体现到列表)和 O(N 平方) 时间(获取L.index每个项目的) . 虽然过早的优化是编程中万恶之源,但当 O(N log N) 可用时故意选择 O(N 平方) 方法,这对可扩展性来说太过分了!-)
L.index
最后,对于那些更喜欢“oneliners”而不是清晰度和性能的人,还有一个额外的 1-liner 版本,带有适当的名称:-)。
from itertools import groupby as g def most_common_oneliner(L): return max(g(sorted(L)), key=lambda(x, v):(len(list(v)),-L.index(x)))[0]