提出了这么多解决方案,我很惊讶没有人提出我认为显而易见的解决方案(对于不可散列但可比较的元素)——[itertools.groupby][1]。 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 语句以更好地查看运行中的机制;例如,with 打印未注释:
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,结果必须是最早出现的)。
groupby 仅按项目分组(通过operator.itemgetter)。辅助函数,在max 计算期间每个分组调用一次,接收并在内部解包一个组 - 一个包含两个项目的元组 (item, iterable) 其中可迭代的项目也是两个项目的元组,(item, original index) [[@ 的项目987654333@]].
然后辅助函数使用循环来确定组的可迭代项中的条目数,和最小原始索引;它将这些作为组合的“质量键”返回,最小索引符号已更改,因此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 平方) 时间(以获得 @每个项目的987654336@)。虽然过早的优化是编程中万恶之源,但当 O(N log N) 可用时故意选择 O(N 平方) 方法,这对可扩展性来说太过分了!-)
最后,对于那些更喜欢“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]