在列表中找到最常见的元素

258 投票
27 回答
402231 浏览
提问于 2025-04-15 14:48

在Python列表中,有什么高效的方法可以找到出现频率最高的元素呢?

我的列表中的元素可能无法被哈希,所以不能使用字典。如果出现频率相同的情况,应该返回索引最小的那个元素。举个例子:

>>> most_common(['duck', 'duck', 'goose'])
'duck'
>>> most_common(['goose', 'duck', 'duck', 'goose'])
'goose'

27 个回答

237

借鉴自这里,这个方法可以在Python 2.7中使用:

from collections import Counter

def Most_Common(lst):
    data = Counter(lst)
    return data.most_common(1)[0][0]

这个方法的速度比Alex的解决方案快4到6倍,比newacct提出的单行代码快50倍。

在CPython 3.6及以上版本(任何Python 3.7及以上版本)中,如果出现多个相同的元素,这个方法会选择第一个出现的元素。如果你在使用旧版本的Python,并且想在出现相同元素的情况下保留列表中第一个出现的元素,你需要进行两次遍历来保持顺序:

# Only needed pre-3.6!
def most_common(lst):
    data = Counter(lst)
    return max(lst, key=data.get)
552

这是一个更简单的一行代码:

def most_common(lst):
    return max(set(lst), key=lst.count)
116

有这么多解决方案被提出,我很惊讶居然没有人提到我认为很明显的一个方法(对于那些不能哈希但可以比较的元素)—— [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 语句可以帮助你更好地理解代码的运作;例如,取消注释后:

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),其中 iterable 的项目也是两个项目的元组 (item, original index) [[就是 SL 的项目]]。

然后,辅助函数使用循环来确定组的 iterable 中条目的数量,以及最小的原始索引;它将这些作为组合的“质量键”返回,并将最小索引的符号反转,这样 max 操作就会认为在原始列表中出现得更早的项目是“更好”的。

如果不那么担心时间和空间的复杂度,这段代码可以简单得多,比如……:

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) 的辅助空间(为了把组的 iterable 转换为列表)和 O(N²) 的时间(为了获取每个项目的 L.index)。虽然过早优化是编程中的万恶之源,但在有 O(N log N) 的方法可用时,故意选择 O(N²) 的方法实在是太不符合可扩展性原则了!-)

最后,对于那些喜欢“单行代码”而不是清晰和性能的人,这里有一个额外的单行版本,名字也稍微搞得复杂一点:-)。

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]

撰写回答