Avec autant de solutions proposées, je suis étonné que personne n'ait proposé ce que je considérerais comme évident (pour des éléments non hachables mais comparables) - [ itertools.groupby
] [1]. itertools
offre des fonctionnalités rapides et réutilisables et vous permet de déléguer une logique délicate à des composants de bibliothèque standard bien testés. Considérez par exemple:
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]
Cela pourrait être écrit de manière plus concise, bien sûr, mais je vise une clarté maximale. Les deux print
déclarations peuvent être décommentées pour mieux voir le mécanisme en action; par exemple, avec des impressions non commentées:
print most_common(['goose', 'duck', 'duck', 'goose'])
émet:
SL: [('duck', 1), ('duck', 2), ('goose', 0), ('goose', 3)]
item 'duck', count 2, minind 1
item 'goose', count 2, minind 0
goose
Comme vous le voyez, SL
est une liste de paires, chaque paire un élément suivi de l'index de l'élément dans la liste d'origine (pour implémenter la condition clé selon laquelle, si les éléments "les plus courants" avec le même nombre le plus élevé sont> 1, le résultat doit être le plus ancien).
groupby
groupes par élément uniquement (via operator.itemgetter
). La fonction auxiliaire, appelée une fois par regroupement pendant le max
calcul, reçoit et décompresse en interne un groupe - un tuple avec deux éléments (item, iterable)
où les éléments de l'itérable sont également des tuples à deux éléments, (item, original index)
[[les éléments de SL
]].
Ensuite, la fonction auxiliaire utilise une boucle pour déterminer à la fois le nombre d'entrées dans l'itérable du groupe et l'index d'origine minimum; il renvoie ceux-ci sous forme de «clé de qualité» combinée, avec le signe d'index min modifié de sorte que l' max
opération considère «meilleurs» les éléments qui se sont produits plus tôt dans la liste d'origine.
Ce code pourrait être beaucoup plus simple s'il s'inquiétait un peu moins des grands problèmes de temps et d'espace, par exemple ...:
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]
même idée de base, juste exprimée plus simplement et de manière plus compacte ... mais, hélas, un espace auxiliaire O (N) supplémentaire (pour incarner les itérables des groupes dans les listes) et le temps O (N au carré) (pour obtenir le L.index
de chaque élément) . Alors que l'optimisation prématurée est la racine de tous les maux de la programmation, choisir délibérément une approche O (N au carré) lorsqu'une O (N log N) est disponible va trop à l'encontre de l'évolutivité! -)
Enfin, pour ceux qui préfèrent les "oneliners" à la clarté et à la performance, une version bonus 1-liner avec des noms convenablement mutilés :-).
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]