Trova l'elemento più comune in un elenco


174

Qual è un modo efficiente per trovare l'elemento più comune in un elenco Python?

Le voci del mio elenco potrebbero non essere hash, quindi non posso usare un dizionario. Anche in caso di pareggi l'articolo deve essere restituito con l'indice più basso. Esempio:

>>> most_common(['duck', 'duck', 'goose'])
'duck'
>>> most_common(['goose', 'duck', 'duck', 'goose'])
'goose'

2
Se gli elementi nell'elenco non sono hash, come determineresti quando sono "uguali"? La perdita di efficienza nel determinare l'uguaglianza per gli oggetti non cancellabili probabilmente negherebbe l'efficienza che speri di ottenere con un buon algoritmo :)
HS.

3
Penso che significhi che gli oggetti possono essere mutabili e quindi non adatti a essere chiavi in ​​una hashmap ...
fortran,

1
sì, questo è ciò che intendevo - a volte conterrà elenchi
hoju,


Risposte:


96

Con così tante soluzioni proposte, sono stupito che nessuno abbia proposto ciò che considererei ovvio (per elementi non hashable ma comparabili) - [ itertools.groupby] [1]. itertoolsoffre funzionalità veloci e riutilizzabili e consente di delegare una logica complessa a componenti di libreria standard ben collaudati. Considera ad esempio:

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]

Questo potrebbe essere scritto in modo più conciso, ovviamente, ma sto puntando alla massima chiarezza. Le due printaffermazioni possono essere decommentate per vedere meglio i macchinari in azione; ad esempio, con stampe non commentate:

print most_common(['goose', 'duck', 'duck', 'goose'])

emette:

SL: [('duck', 1), ('duck', 2), ('goose', 0), ('goose', 3)]
item 'duck', count 2, minind 1
item 'goose', count 2, minind 0
goose

Come vedi, SLè un elenco di coppie, ciascuna coppia di un elemento seguita dall'indice dell'articolo nell'elenco originale (per implementare la condizione chiave che, se gli elementi "più comuni" con lo stesso conteggio più alto sono> 1, il risultato deve essere il primo che si verifica).

groupbyraggruppa solo per l'articolo (via operator.itemgetter). La funzione ausiliaria, chiamata una volta per raggruppamento durante il maxcalcolo, riceve e disimballa internamente un gruppo: una tupla con due elementi in (item, iterable)cui gli elementi dell'iterabile sono anche tuple a due elementi, (item, original index)[[gli elementi di SL]].

Quindi la funzione ausiliaria utilizza un ciclo per determinare sia il conteggio delle voci nell'iterabile del gruppo, sia l'indice minimo originale; restituisce quelli come "chiave di qualità" combinata, con il segno dell'indice minimo modificato in modo che l' maxoperazione consideri "migliori" quegli elementi che si sono verificati in precedenza nell'elenco originale.

Questo codice potrebbe essere molto più semplice se si preoccupasse un po ' meno dei problemi della Big-O nel tempo e nello spazio, ad esempio ...:

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]

stessa idea di base, appena espressa in modo più semplice e compatto ... ma, ahimè, uno spazio ausiliario O (N) in più (per incarnare gli iterabili dei gruppi nelle liste) e il tempo O (N al quadrato) (per ottenere il valore L.indexdi ogni elemento) . Mentre l'ottimizzazione prematura è la radice di tutti i mali della programmazione, scegliere deliberatamente un approccio O (N al quadrato) quando è disponibile uno O (N log N) va semplicemente contro il grano della scalabilità! -)

Infine, per coloro che preferiscono gli "oneliners" alla chiarezza e alle prestazioni, una versione bonus da 1 linea con nomi opportunamente alterati :-).

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]

3
Questo si interrompe su Python3 se l'elenco ha tipi diversi.
AlexLordThorsen,

2
groupbyrichiede prima l'ordinamento (O (NlogN)); usare un Counter()con most_common()può batterlo perché usa un heapq per trovare l'elemento con la frequenza più alta (solo per 1 elemento, questo è il tempo O (N)). Poiché Counter()ora è fortemente ottimizzato (il conteggio avviene in un ciclo C), può facilmente battere questa soluzione anche per piccoli elenchi. Lo fa saltare fuori dall'acqua per grandi liste.
Martijn Pieters

Solo il requisito dell '"indice più basso" per i legami rende questa soluzione valida solo per questo problema. Per il caso più generale dovresti assolutamente usare l'approccio Counter.
Martijn Pieters

@MartijnPieters Forse ti sei perso la parte della domanda in cui diceva che gli articoli potrebbero non essere lavabili.
mercoledì

@wim right, e se gli articoli non sono lavabili. Il che rende i voti sul set e l'approccio max ancora più incongrui.
Martijn Pieters

442

Una linea semplice:

def most_common(lst):
    return max(set(lst), key=lst.count)

24
Il PO ha dichiarato che [..] in caso di pareggi l'articolo con l'indice più basso dovrebbe essere restituito. Questo codice, in generale, non soddisfa tale requisito.
Stephan202,

2
Inoltre, l'OP ha dichiarato che gli elementi devono essere hash: i set devono contenere oggetti hash.
Eric O Lebigot,

2
Inoltre, questo approccio è algoritmicamente lento (per ogni elemento in set(lst), l'intero elenco deve essere ricontrollato) ... Probabilmente abbastanza veloce per la maggior parte degli usi, però ...
Eric O Lebigot,

9
Puoi sostituirlo set(lst)con lste funzionerà anche con elementi non cancellabili; anche se più lento.
Newacct,

24
Questo può sembrare attraente ma da un punto di vista algoritmico questo è un consiglio terribile. list.count()deve attraversare l'elenco per intero , e lo fai per ogni singolo elemento univoco nell'elenco. Questo rende questa una soluzione O (NK) (O (N ^ 2) nel peggiore dei casi). L'uso di a Counter()richiede solo O (N)!
Martijn Pieters

185

Prendendo in prestito da qui , questo può essere usato con Python 2.7:

from collections import Counter

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

Funziona circa 4-6 volte più velocemente delle soluzioni di Alex ed è 50 volte più veloce del one-liner proposto da newacct.

Per recuperare l'elemento che si presenta per primo nell'elenco in caso di vincoli:

def most_common(lst):
    data = Counter(lst)
    return max(lst, key=data.get)

3
Questo potrebbe essere utile per alcuni ma ... sfortunatamente Counter è una sottoclasse di dict e l'OP ha detto che non poteva usare i dizionari (poiché gli oggetti potrebbero non essere cancellabili).
Danimal,

13
Ama questo. Il one-liner di @newacct sopra può essere semplice, ma funziona in O (n ^ 2); cioè, dove n è la lunghezza dell'elenco. Questa soluzione è O (n).
BoltzmannBrain

5
Come la semplicità e la velocità ... forse non è l'ideale per OP. Ma mi sta benissimo!
Thom,

non restituisce l'elemento indicizzato più basso. most_common restituisce un elenco non ordinato e afferrando (1) restituisce semplicemente quello che vorrebbe.
AgentBawls,

@AgentBawls: most_commonè ordinato per conteggio, non ordinato. Detto questo, non sceglierà il primo elemento in caso di legami; Ho aggiunto un altro modo per utilizzare il contatore che seleziona il primo elemento.
user2357112 supporta Monica il

58

Quello che vuoi è noto nelle statistiche come mode, e Python ovviamente ha una funzione integrata per fare esattamente questo per te:

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

Si noti che se non esiste un "elemento più comune" come i casi in cui i primi due sono legati , questo aumenterà StatisticsError, poiché statisticamente parlando, in questo caso non esiste alcuna modalità .


8
ciò non soddisfa il requisito del PO di cosa restituire quando esiste più di un valore più comune: un dato statistico. Statistica L'errore viene generato
Keith Hall

5
Oops, mancato il requisito durante la lettura. Credo comunque che questa risposta abbia valore, poiché nessuno l'ha suggerito in questa domanda ed è una buona soluzione al problema per le persone con requisiti meno restrittivi. Questo è uno dei migliori risultati per "oggetto più comune nella lista pitone"
Luiz Berti

1
In tal caso, utilizzare la funzione mode in Panda DataFrames.
Elmex80s

1
Voto positivo, questo dovrebbe essere più alto. E non è così difficile da soddisfare il requisito del PO con semplici try-except (vedi il mio stackoverflow.com/a/52952300/6646912 )
Krassowski

1
@BreakBadSP la tua risposta utilizza più memoria a causa dell'ulteriore seted è plausibilmente O(n^3).
Luiz Berti,

9

Se non sono cancellabili, puoi ordinarli ed eseguire un singolo ciclo sul risultato contando gli oggetti (gli oggetti identici saranno uno accanto all'altro). Ma potrebbe essere più veloce renderli lavabili e usare un dict.

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

Ecco un modo più semplice ideone.com/Nq81vf , rispetto alla Counter()soluzione di Alex
Miguel,

6

Questa è una soluzione 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

(invertito viene utilizzato per assicurarsi che restituisca l'elemento di indice più basso)


6

Senza il requisito per l'indice più basso, è possibile utilizzare collections.Counterper questo:

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'

Facile e veloce. Si r il mio padrino 😏✌
chainstair

1
questa risposta richiede più voti poiché affronta l'attività generale di conteggio delle occorrenze di elementi in un elenco utilizzando un modulo standard e 2 righe di codice
pcko1,

5

Ordina una copia dell'elenco e trova la corsa più lunga. È possibile decorare l'elenco prima di ordinarlo con l'indice di ciascun elemento, quindi scegliere la corsa che inizia con l'indice più basso in caso di pareggio.


Gli articoli potrebbero non essere comparabili.
Pawel Furmaniak,

4

Una fodera:

def most_common (lst):
    return max(((item, lst.count(item)) for item in set(lst)), key=lambda a: a[1])[0]

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'

3

Semplice soluzione a una riga

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

Restituirà l'elemento più frequente con la sua frequenza.


2

Probabilmente non ti servirà più, ma questo è quello che ho fatto per un problema simile. (Sembra più lungo di quanto non sia a causa dei commenti.)

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

1
potresti usare counter [item] = counter.get (item, 0) + 1 per sostituire la parte try / tranne
XueYu,

1

Basandosi sulla risposta di Luiz , ma soddisfacendo la condizione " in caso di pareggi l'articolo con l'indice più basso dovrebbe essere restituito ":

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

Esempio:

>>> most_common(['a', 'b', 'b'])
'b'
>>> most_common([1, 2])
1
>>> most_common([])
StatisticsError: no mode for empty data

0

Qui:

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

Ho una vaga sensazione che ci sia un metodo da qualche parte nella libreria standard che ti darà il conteggio di ogni elemento, ma non riesco a trovarlo.


3
'max' è un metodo. Cambieresti il ​​nome della variabile?
Pratik Deoghare,

1
Si noti che set () richiede anche elementi hash, in questo caso la soluzione non funzionerebbe.
Lukáš Lalinský,

Aspetta, mi mancava quella parte del non essere seccabile. Ma se gli oggetti hanno uguaglianza, dovrebbe essere facile renderli hash.
Lennart Regebro,

0

Questa è l'ovvia soluzione lenta (O (n ^ 2)) se né l'ordinamento né l'hashing sono fattibili, ma ==è disponibile il confronto di uguaglianza ( ):

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]

Ma rendere i tuoi oggetti hash o ordinabili (come raccomandato da altre risposte) renderebbe quasi sempre più veloce la ricerca dell'elemento più comune se la lunghezza della tua lista (n) è grande. O (n) in media con hashing e O (n * log (n)) nel peggiore dei casi per l'ordinamento.


Per il downvoter: cosa c'è di sbagliato in questa risposta? Qualcuna delle altre risposte fornisce una soluzione quando né l'ordinamento né l'hashing sono fattibili?
punti

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'

Ciò ha una terribile caratteristica prestazionale quando n è grande e anche il numero di elementi unici è grande: O (n) per la conversione in un insieme e O (m * n) = O (n ^ 2) per il conteggio (dove m è il numero di uniques). Ordina e cammina è O (n registro n) per l'ordinamento e 0 (n) per il cammino.
jmucchiello,

1
Si hai ragione. Ora so che questa è una soluzione terribile e perché. Grazie per il commento !! :-)
Pratik Deoghare

0

Ho dovuto farlo in un programma recente. Lo ammetto, non riuscivo a capire la risposta di Alex, quindi questo è quello con cui sono finito.

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

L'ho confrontato con la soluzione di Alex ed è circa il 10-15% più veloce per gli elenchi brevi, ma una volta superati i 100 elementi o più (testato fino a 200000) è circa il 20% più lento.


-1

Ciao questa è una soluzione molto semplice con grande 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

Dove numera l'elemento nell'elenco che si ripete per la maggior parte del tempo


-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"


tutte le altre risposte. vuoi che li colleghi?
12 rombi in griglia senza angoli

-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)

6
Fornisci alcune informazioni sul tuo codice, solo pubblicare il codice non è una risposta completa
jhhoff02

1
C'è un motivo per cui qualcuno dovrebbe usarlo oltre le altre 15 risposte?
Tutti i lavoratori sono essenziali il

-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)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.