Com tantas soluções propostas, fico surpreso que ninguém tenha proposto o que eu consideraria óbvio (para elementos não-laváveis, mas comparáveis) - [ itertools.groupby
] [1]. itertools
oferece funcionalidade rápida e reutilizável e permite delegar alguma lógica complicada a componentes de biblioteca padrão bem testados. Considere, por exemplo:
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]
Isso pode ser escrito de forma mais concisa, é claro, mas estou buscando a máxima clareza. As duas print
declarações podem ser descomentadas para ver melhor o mecanismo em ação; por exemplo, com impressões não comentadas:
print most_common(['goose', 'duck', 'duck', 'goose'])
emite:
SL: [('duck', 1), ('duck', 2), ('goose', 0), ('goose', 3)]
item 'duck', count 2, minind 1
item 'goose', count 2, minind 0
goose
Como você vê, SL
é uma lista de pares, cada par é um item seguido pelo índice do item na lista original (para implementar a condição principal de que, se os itens "mais comuns" com a mesma contagem mais alta forem> 1, o resultado deverá ser seja o mais antigo).
groupby
agrupa apenas pelo item (via operator.itemgetter
). A função auxiliar, chamada uma vez por agrupamento durante o max
cálculo, recebe e descompacta internamente um grupo - uma tupla com dois itens em (item, iterable)
que os itens do iterável também são tuplas de dois itens (item, original index)
[[os itens de SL
]].
Em seguida, a função auxiliar usa um loop para determinar a contagem de entradas no iterável do grupo e o índice original mínimo; retorna esses itens como "chave de qualidade" combinada, com o sinal de índice mínimo alterado para que omax
itens operação considere "melhor" os itens que ocorreram anteriormente na lista original.
Este código poderia ser muito mais simples se se preocupasse um pouco menos com problemas de grande O no tempo e no espaço, por exemplo ...
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]
mesma idéia básica, apenas expressa de maneira mais simples e compacta ... mas, infelizmente, um espaço auxiliar O (N) extra (para incorporar as iteráveis dos grupos às listas) e o tempo O (N ao quadrado) (para obter o L.index
item de cada item) . Embora a otimização prematura seja a raiz de todos os males da programação, escolher deliberadamente uma abordagem O (N ao quadrado) quando uma O (N log N) disponível está apenas indo muito contra o grão da escalabilidade! -)
Finalmente, para aqueles que preferem "oneliners" à clareza e desempenho, uma versão bônus de 1 liner com nomes adequadamente mutilados :-).
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]