Znajdź najpopularniejszy element na liście

174

Jaki jest skuteczny sposób na znalezienie najbardziej powszechnego elementu na liście w Pythonie?

Moje elementy listy mogą nie podlegać haszowaniu, więc nie można używać słownika. Również w przypadku losowań należy zwrócić pozycję o najniższym indeksie. Przykład:

>>> most_common(['duck', 'duck', 'goose'])
'duck'
>>> most_common(['goose', 'duck', 'duck', 'goose'])
'goose'
hoju
źródło
2
Jeśli elementów na liście nie można haszować, jak określić, czy są „równe”? Utrata wydajności w określaniu równości dla elementów, których nie da się skasować, prawdopodobnie zniweczy jakąkolwiek wydajność, którą masz nadzieję uzyskać za pomocą dobrego algorytmu :)
HS.
3
Myślę, że ma na myśli, że przedmioty mogą być zmienne, a zatem nie mogą być kluczami w hashmap ...
fortran
1
tak to miałem na myśli - czasami będzie zawierał listy
hoju
Najlepszy sposób stackoverflow.com/a/50227350/7918560
BreakBadSP

Odpowiedzi:

96

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]
Alex Martelli
źródło
3
To psuje się w Python3, jeśli twoja lista ma różne typy.
AlexLordThorsen
2
groupbywymaga najpierw sortowania (O (NlogN)); użycie Counter()with most_common()może to pokonać, ponieważ używa heapq do znalezienia elementu o najwyższej częstotliwości (tylko dla 1 elementu to czas O (N)). Ponieważ Counter()obecnie jest mocno zoptymalizowany (liczenie odbywa się w pętli C), może z łatwością pokonać to rozwiązanie nawet w przypadku małych list. Wydmuchuje go z wody w przypadku dużych list.
Martijn Pieters
Tylko wymóg „najniższego wskaźnika” dla krawatów sprawia, że ​​jest to odpowiednie rozwiązanie tylko dla tego problemu. W bardziej ogólnym przypadku zdecydowanie powinieneś użyć podejścia Counter.
Martijn Pieters
@MartijnPieters Być może przegapiłeś część pytania, w której mówiło się, że elementy mogą być niekasowane.
wim
@wim dobrze, a jeśli elementy są niemożliwe do ukrycia. Co sprawia, że ​​głosy na planie i max zbliżają się jeszcze bardziej nie na miejscu.
Martijn Pieters
442

Prostszy, jednoliniowy:

def most_common(lst):
    return max(set(lst), key=lst.count)
newacct
źródło
24
PO stwierdził, że […] w przypadku losowania należy zwrócić pozycję o najniższym indeksie. Ten kod na ogół nie spełnia tego wymagania.
Stephan202
2
Ponadto OP stwierdził, że elementy muszą być hashowalne: zestawy muszą zawierać haszowalne obiekty.
Eric O Lebigot
2
Dodatkowo, to podejście jest algorytmicznie powolne (dla każdego elementu w set(lst), cała lista musi być ponownie sprawdzona)… Prawdopodobnie wystarczająco szybkie dla większości zastosowań, chociaż…
Eric O Lebigot
9
Można wymienić set(lst)z lsti będzie pracować z elementami non-hashable też; aczkolwiek wolniej.
newacct
24
Może to wyglądać atrakcyjnie, ale z algorytmicznego punktu widzenia to straszna rada. list.count()musi przechodzić listy w całości , i to zrobić dla każdej unikalnej pozycji na liście. To sprawia, że ​​jest to rozwiązanie O (NK) (w najgorszym przypadku O (N ^ 2)). Użycie a Counter()zajmuje tylko O ​​(N) czasu!
Martijn Pieters
185

Pożyczając stąd , można tego użyć z Pythonem 2.7:

from collections import Counter

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

Działa około 4-6 razy szybciej niż rozwiązania Alexa i jest 50 razy szybszy niż jednolinijkowy proponowany przez newacct.

Aby pobrać element, który występuje jako pierwszy na liście w przypadku remisów:

def most_common(lst):
    data = Counter(lst)
    return max(lst, key=data.get)
Alex
źródło
3
Może to być przydatne dla niektórych, ale ... niestety Counter jest podklasą dict, a OP powiedział, że nie może używać słowników (ponieważ elementy mogą nie być hashowane).
Danimal,
13
Kocham to. Jednowierszowy powyższy @newacct może być prosty, ale działa w O (n ^ 2); to znaczy, gdzie n jest długością listy. To rozwiązanie to O (n).
BoltzmannBrain
5
Podobnie jak prostota i szybkość ... może nie jest idealna dla OP. Ale mi pasuje!
Thom
nie zwraca najniższego zindeksowanego elementu. most_common zwraca nieuporządkowaną listę, a grabbing (1) po prostu zwraca cokolwiek by chciał.
AgentBawls,
@AgentBawls: most_commonjest sortowane według liczby, a nie nieuporządkowane. To powiedziawszy, nie wybierze pierwszego elementu w przypadku remisów; Dodałem inny sposób korzystania z licznika, który wybiera pierwszy element.
user2357112 obsługuje Monikę
58

To, czego chcesz, jest znane w statystykach jako tryb, a Python ma oczywiście wbudowaną funkcję, która robi dokładnie to za Ciebie:

>>> from statistics import mode
>>> mode([1, 2, 2, 3, 3, 3, 3, 3, 4, 5, 6, 6, 6])
3

Zwróć uwagę, że jeśli nie ma „najbardziej powszechnego elementu”, takiego jak przypadki, w których dwie pierwsze są równe , to wzrośnie StatisticsError, ponieważ statystycznie rzecz biorąc, w tym przypadku nie ma trybu .

Luiz Berti
źródło
8
ten nie spełnia wymogu PO za co wrócić, gdy istnieje więcej niż jeden najczęstszą wartość - statistics.StatisticsError jest podniesiona
Keith Hall
5
Ups, pominięto wymaganie podczas czytania. Nadal uważam jednak, że ta odpowiedź ma wartość, ponieważ nikt jej nie zasugerował w tym pytaniu i jest dobrym rozwiązaniem problemu dla osób o najmniej restrykcyjnych wymaganiach. To jest jeden z najlepszych wyników dla „najpopularniejszej pozycji na liście python”
Luiz Berti
1
W takim przypadku użyj funkcji trybu w pandas DataFrames.
Elmex80s
1
Głos za zgodą, ten powinien być wyższy. I nie jest tak trudno spełnić wymagania OP za pomocą prostego try-z wyjątkiem (patrz mój stackoverflow.com/a/52952300/6646912 )
krassowski
1
@BreakBadSP Twoja odpowiedź zużywa więcej pamięci z powodu dodatkowego seti jest prawdopodobna O(n^3).
Luiz Berti
9

Jeśli nie można ich haszować, można je posortować i wykonać pojedynczą pętlę po wyniku zliczając elementy (identyczne elementy będą obok siebie). Ale może być szybsze uczynienie ich haszowalnymi i użycie dyktowania.

def most_common(lst):
    cur_length = 0
    max_length = 0
    cur_i = 0
    max_i = 0
    cur_item = None
    max_item = None
    for i, item in sorted(enumerate(lst), key=lambda x: x[1]):
        if cur_item is None or cur_item != item:
            if cur_length > max_length or (cur_length == max_length and cur_i < max_i):
                max_length = cur_length
                max_i = cur_i
                max_item = cur_item
            cur_length = 1
            cur_i = i
            cur_item = item
        else:
            cur_length += 1
    if cur_length > max_length or (cur_length == max_length and cur_i < max_i):
        return cur_item
    return max_item
Lukáš Lalinský
źródło
Oto prostszy sposób ideone.com/Nq81vf , w porównaniu z Counter()rozwiązaniem Alexa
Miguel
6

To jest rozwiązanie O (n).

mydict   = {}
cnt, itm = 0, ''
for item in reversed(lst):
     mydict[item] = mydict.get(item, 0) + 1
     if mydict[item] >= cnt :
         cnt, itm = mydict[item], item

print itm

(odwrócony jest używany, aby upewnić się, że zwraca najniższą pozycję indeksu)

ThisIsMeMoony
źródło
6

Bez wymogu dotyczącego najniższego indeksu możesz użyć collections.Counterdo tego:

from collections import Counter

a = [1936, 2401, 2916, 4761, 9216, 9216, 9604, 9801] 

c = Counter(a)

print(c.most_common(1)) # the one most common element... 2 would mean the 2 most common
[(9216, 2)] # a set containing the element, and it's count in 'a'
Ojciec chrzestny
źródło
Łatwo i szybko. Rw mój chrzestny 😏✌
chainstair
1
ta odpowiedź wymaga większej liczby głosów pozytywnych, ponieważ dotyczy ogólnego zadania zliczania wystąpień elementów na liście przy użyciu standardowego modułu i 2 wierszy kodu
pcko1
5

Sortuj kopię listy i znajdź najdłuższy bieg. Możesz ozdobić listę przed posortowaniem jej indeksem każdego elementu, a następnie wybrać przebieg, który w przypadku remisu zaczyna się od najniższego indeksu.

Boojum
źródło
Pozycje mogą nie być porównywalne.
Paweł Furmaniak
4

Jednowierszowy:

def most_common (lst):
    return max(((item, lst.count(item)) for item in set(lst)), key=lambda a: a[1])[0]
willurd
źródło
3
# use Decorate, Sort, Undecorate to solve the problem

def most_common(iterable):
    # Make a list with tuples: (item, index)
    # The index will be used later to break ties for most common item.
    lst = [(x, i) for i, x in enumerate(iterable)]
    lst.sort()

    # lst_final will also be a list of tuples: (count, index, item)
    # Sorting on this list will find us the most common item, and the index
    # will break ties so the one listed first wins.  Count is negative so
    # largest count will have lowest value and sort first.
    lst_final = []

    # Get an iterator for our new list...
    itr = iter(lst)

    # ...and pop the first tuple off.  Setup current state vars for loop.
    count = 1
    tup = next(itr)
    x_cur, i_cur = tup

    # Loop over sorted list of tuples, counting occurrences of item.
    for tup in itr:
        # Same item again?
        if x_cur == tup[0]:
            # Yes, same item; increment count
            count += 1
        else:
            # No, new item, so write previous current item to lst_final...
            t = (-count, i_cur, x_cur)
            lst_final.append(t)
            # ...and reset current state vars for loop.
            x_cur, i_cur = tup
            count = 1

    # Write final item after loop ends
    t = (-count, i_cur, x_cur)
    lst_final.append(t)

    lst_final.sort()
    answer = lst_final[0][2]

    return answer

print most_common(['x', 'e', 'a', 'e', 'a', 'e', 'e']) # prints 'e'
print most_common(['goose', 'duck', 'duck', 'goose']) # prints 'goose'
steveha
źródło
3

Proste rozwiązanie w jednej linii

moc= max([(lst.count(chr),chr) for chr in set(lst)])

Zwróci najczęściej występujący element z jego częstotliwością.

Shivam Agrawal
źródło
2

Prawdopodobnie już tego nie potrzebujesz, ale to właśnie zrobiłem dla podobnego problemu. (Wygląda na dłuższą niż jest z powodu komentarzy.)

itemList = ['hi', 'hi', 'hello', 'bye']

counter = {}
maxItemCount = 0
for item in itemList:
    try:
        # Referencing this will cause a KeyError exception
        # if it doesn't already exist
        counter[item]
        # ... meaning if we get this far it didn't happen so
        # we'll increment
        counter[item] += 1
    except KeyError:
        # If we got a KeyError we need to create the
        # dictionary key
        counter[item] = 1

    # Keep overwriting maxItemCount with the latest number,
    # if it's higher than the existing itemCount
    if counter[item] > maxItemCount:
        maxItemCount = counter[item]
        mostPopularItem = item

print mostPopularItem
Ed Holden
źródło
1
możesz użyć counter [item] = counter.get (item, 0) + 1, aby zastąpić próbę / z wyjątkiem części
XueYu
1

Opierając się na odpowiedzi Luiza , ale spełniając warunek „ w przypadku remisów pozycja o najniższym indeksie powinna zostać zwrócona ”:

from statistics import mode, StatisticsError

def most_common(l):
    try:
        return mode(l)
    except StatisticsError as e:
        # will only return the first element if no unique mode found
        if 'no unique mode' in e.args[0]:
            return l[0]
        # this is for "StatisticsError: no mode for empty data"
        # after calling mode([])
        raise

Przykład:

>>> most_common(['a', 'b', 'b'])
'b'
>>> most_common([1, 2])
1
>>> most_common([])
StatisticsError: no mode for empty data
krassowski
źródło
0

Tutaj:

def most_common(l):
    max = 0
    maxitem = None
    for x in set(l):
        count =  l.count(x)
        if count > max:
            max = count
            maxitem = x
    return maxitem

Mam niejasne wrażenie, że gdzieś w standardowej bibliotece jest metoda, która poda liczbę każdego elementu, ale nie mogę jej znaleźć.

Lennart Regebro
źródło
3
„max” to metoda. Czy zmieniłbyś nazwę zmiennej?
Pratik Deoghare
1
Zwróć uwagę, że metoda set () wymaga również elementów, które można mieszać, aby rozwiązanie nie działało w tym przypadku.
Lukáš Lalinský
Czekaj, tęskniłem za tym, że nie dało się haszować. Ale jeśli obiekty są równe, powinno być łatwe do skasowania.
Lennart Regebro
0

Jest to oczywiste powolne rozwiązanie (O (n ^ 2)), jeśli ani sortowanie, ani haszowanie nie są możliwe, ale ==dostępne jest porównanie równości ( ):

def most_common(items):
  if not items:
    raise ValueError
  fitems = [] 
  best_idx = 0
  for item in items:   
    item_missing = True
    i = 0
    for fitem in fitems:  
      if fitem[0] == item:
        fitem[1] += 1
        d = fitem[1] - fitems[best_idx][1]
        if d > 0 or (d == 0 and fitems[best_idx][2] > fitem[2]):
          best_idx = i
        item_missing = False
        break
      i += 1
    if item_missing:
      fitems.append([item, 1, i])
  return items[best_idx]

Jednak umożliwienie hashowania lub sortowania elementów (zgodnie z zaleceniami innych odpowiedzi) prawie zawsze przyspieszyłoby znalezienie najczęściej używanego elementu, jeśli długość listy (n) jest duża. O (n) średnio z haszowaniem, a O (n * log (n)) w najgorszym przypadku do sortowania.

pkt
źródło
Do przeciwnika: co jest nie tak z tą odpowiedzią? Czy którakolwiek z pozostałych odpowiedzi zapewnia rozwiązanie, gdy ani sortowanie, ani haszowanie nie są możliwe?
pts
0
>>> li  = ['goose', 'duck', 'duck']

>>> def foo(li):
         st = set(li)
         mx = -1
         for each in st:
             temp = li.count(each):
             if mx < temp:
                 mx = temp 
                 h = each 
         return h

>>> foo(li)
'duck'
Pratik Deoghare
źródło
Ma to straszną charakterystykę wydajności, gdy n jest duże, a liczba unikalnych elementów jest również duża: O (n) dla konwersji do zbioru i O (m * n) = O (n ^ 2) dla liczby (gdzie m to liczba unikatów). Sortowanie i spacer to O (n log n) dla sortowania i 0 (n) dla spaceru.
jmucchiello
1
Tak, masz rację. Teraz wiem, że to okropne rozwiązanie i dlaczego. Dziękuję za komentarz!! :-)
Pratik Deoghare
0

Musiałem to zrobić w ostatnim programie. Przyznaję, nie mogłem zrozumieć odpowiedzi Alexa, więc na tym skończyłem.

def mostPopular(l):
    mpEl=None
    mpIndex=0
    mpCount=0
    curEl=None
    curCount=0
    for i, el in sorted(enumerate(l), key=lambda x: (x[1], x[0]), reverse=True):
        curCount=curCount+1 if el==curEl else 1
        curEl=el
        if curCount>mpCount \
        or (curCount==mpCount and i<mpIndex):
            mpEl=curEl
            mpIndex=i
            mpCount=curCount
    return mpEl, mpCount, mpIndex

Zmierzyłem czas z rozwiązaniem Alexa i jest około 10-15% szybszy w przypadku krótkich list, ale gdy przejdziesz ponad 100 elementów lub więcej (przetestowano do 200000), jest około 20% wolniej.

pauleohare
źródło
-1

Cześć, to bardzo proste rozwiązanie z dużym O (n)

L = [1, 4, 7, 5, 5, 4, 5]

def mode_f(L):
# your code here
    counter = 0
    number = L[0]
    for i in L:
        amount_times = L.count(i)
        if amount_times > counter:
            counter = amount_times
            number = i

    return number

Gdzie numerować element na liście, który powtarza się przez większość czasu

Scena
źródło
-2
def mostCommonElement(list):
  count = {} // dict holder
  max = 0 // keep track of the count by key
  result = None // holder when count is greater than max
  for i in list:
    if i not in count:
      count[i] = 1
    else:
      count[i] += 1
    if count[i] > max:
      max = count[i]
      result = i
  return result

mostCommonElement (["a", "b", "a", "c"]) -> "a"

Israel Manzo
źródło
wszystkie inne odpowiedzi. czy mam je połączyć?
12 rombów w siatce bez rogów
-3
 def most_common(lst):
    if max([lst.count(i)for i in lst]) == 1:
        return False
    else:
        return max(set(lst), key=lst.count)
Ecanales
źródło
6
Podaj trochę informacji o swoim kodzie, samo wysłanie kodu nie jest pełną odpowiedzią
jhhoff02
1
Czy jest jakiś powód, dla którego ktoś powinien użyć tego zamiast 15 innych odpowiedzi?
Wszyscy pracownicy są niezbędni
-5
def popular(L):
C={}
for a in L:
    C[a]=L.count(a)
for b in C.keys():
    if C[b]==max(C.values()):
        return b
L=[2,3,5,3,6,3,6,3,6,3,7,467,4,7,4]
print popular(L)
Pronoy
źródło