Przy tak wielu proponowanych rozwiązaniach jestem zdumiony, że nikt nie zaproponował tego, co uważam za oczywiste (dla elementów niekasowalnych, ale porównywalnych) - [ itertools.groupby] [1]. itertoolsoferuje szybką funkcjonalność wielokrotnego użytku i pozwala delegować skomplikowaną logikę do dobrze przetestowanych komponentów bibliotek standardowych. Rozważmy na przykład:
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]
Można to oczywiście napisać bardziej zwięźle, ale dążę do maksymalnej jasności. Te dwa printstwierdzenia można pominąć, aby lepiej zobaczyć maszynę w akcji; na przykład z wydrukami bez komentarzy:
print most_common(['goose', 'duck', 'duck', 'goose'])
emituje:
SL: [('duck', 1), ('duck', 2), ('goose', 0), ('goose', 3)]
item 'duck', count 2, minind 1
item 'goose', count 2, minind 0
goose
Jak widać, SLjest to lista par, każda para to element, po którym następuje indeks elementu na pierwotnej liście (aby zaimplementować kluczowy warunek, że jeśli „najczęściej” elementy o tej samej największej liczbie są> 1, wynik musi być najwcześniej występującym).
groupbygrupuje tylko według elementu (przez operator.itemgetter). Funkcja pomocnicza, wywoływana raz na grupę podczas maxobliczania, odbiera i wewnętrznie rozpakowuje grupę - krotkę z dwoma elementami, (item, iterable)gdzie elementy iterowalne są również krotkami dwuelementowymi, (item, original index)[[elementy SL]].
Następnie funkcja pomocnicza używa pętli do określenia zarówno liczby wpisów w iterowalnej grupie, jak i minimalnego pierwotnego indeksu; zwraca je jako połączony „klucz jakości”, ze zmienionym znakiem indeksu min, więc maxoperacja uzna za „lepsze” te elementy, które wystąpiły wcześniej na pierwotnej liście.
Ten kod mógłby być znacznie prostszy, gdyby martwił się trochę mniej problemami z dużymi O w czasie i przestrzeni, np ...:
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]
ta sama podstawowa idea, po prostu wyrażona prościej i zwięźle ... ale, niestety, dodatkowa przestrzeń pomocnicza O (N) (aby ująć iterowalne grupy do list) i O (N do kwadratu) czas (aby uzyskać L.indexz każdej pozycji) . Podczas gdy przedwczesna optymalizacja jest źródłem wszelkiego zła w programowaniu, celowe wybieranie podejścia O (N do kwadratu), gdy dostępne jest O (N log N), jest po prostu zbyt duże wbrew ziarnu skalowalności! -)
Wreszcie dla tych, którzy wolą „oneliner” od przejrzystości i wydajności, bonusowa wersja 1-liniowa z odpowiednio zniekształconymi nazwami :-).
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]