Con tantas soluciones propuestas, me sorprende que nadie haya propuesto lo que yo consideraría obvio (para elementos no intercambiables pero comparables) - [ itertools.groupby
] [1]. itertools
ofrece una funcionalidad rápida y reutilizable, y le permite delegar algunas lógicas difíciles a componentes de biblioteca estándar bien probados Considere por ejemplo:
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]
Esto podría escribirse de manera más concisa, por supuesto, pero estoy buscando la máxima claridad. Las dos print
declaraciones pueden ser descomentadas para ver mejor la maquinaria en acción; por ejemplo, con impresiones sin comentar:
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 puede ver, SL
es una lista de pares, cada par un elemento seguido del índice del elemento en la lista original (para implementar la condición clave de que, si los elementos "más comunes" con el mismo recuento más alto son> 1, el resultado debe ser el más temprano).
groupby
agrupa por el artículo solamente (vía operator.itemgetter
). La función auxiliar, llamada una vez por agrupación durante el max
cálculo, recibe y desempaqueta internamente un grupo: una tupla con dos elementos (item, iterable)
donde los elementos del iterable también son tuplas de dos elementos, (item, original index)
[[los elementos de SL
]].
Luego, la función auxiliar utiliza un bucle para determinar tanto el recuento de entradas en el iterable del grupo como el índice original mínimo; los devuelve como "clave de calidad" combinada, con el signo de índice mínimo cambiado para que la max
operación considere "mejor" aquellos elementos que ocurrieron anteriormente en la lista original.
Este código podría ser mucho más simple si se preocupara un poco menos por los problemas de grandes O en el tiempo y el espacio, por ejemplo ...:
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]
misma idea básica, expresada de manera más simple y compacta ... pero, por desgracia, un espacio auxiliar O (N) adicional (para incorporar los iterables de los grupos a las listas) y el tiempo O (N al cuadrado) (para obtener el L.index
de cada elemento) . Si bien la optimización prematura es la raíz de todo mal en la programación, elegir deliberadamente un enfoque O (N cuadrado) cuando hay un O (N log N) disponible, ¡va demasiado en contra de la escalabilidad! -)
Finalmente, para aquellos que prefieren "oneliners" a la claridad y el rendimiento, una versión adicional de 1 línea con nombres adecuadamente destrozados :-).
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]