Encontre o elemento mais comum em uma lista

174

Qual é uma maneira eficiente de encontrar o elemento mais comum em uma lista Python?

Os itens da minha lista podem não ser laváveis, portanto, não é possível usar um dicionário. Também no caso de empates, o item com o índice mais baixo deve ser retornado. Exemplo:

>>> most_common(['duck', 'duck', 'goose'])
'duck'
>>> most_common(['goose', 'duck', 'duck', 'goose'])
'goose'
hoju
fonte
2
Se os itens da lista não forem laváveis, como você determinaria quando são 'iguais'? A perda de eficiência na determinação da igualdade para itens não-laváveis ​​provavelmente negaria a eficiência que você espera obter com um bom algoritmo :)
HS.
3
Eu acho que ele significa que os itens podem ser mutável e, portanto, não elegível para ser chaves em uma hashmap ...
fortran
1
sim é isso que eu quis dizer - às vezes ele irá conter listas
Hoju
Melhor maneira stackoverflow.com/a/50227350/7918560
BreakBadSP:

Respostas:

96

Com tantas soluções propostas, fico surpreso que ninguém tenha proposto o que eu consideraria óbvio (para elementos não-laváveis, mas comparáveis) - [ itertools.groupby] [1]. itertoolsoferece funcionalidade rápida e reutilizável e permite delegar alguma lógica complicada a componentes de biblioteca padrão bem testados. Considere, por exemplo:

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]

Isso pode ser escrito de forma mais concisa, é claro, mas estou buscando a máxima clareza. As duas printdeclarações podem ser descomentadas para ver melhor o mecanismo em ação; por exemplo, com impressões não comentadas:

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 você vê, SL é uma lista de pares, cada par é um item seguido pelo índice do item na lista original (para implementar a condição principal de que, se os itens "mais comuns" com a mesma contagem mais alta forem> 1, o resultado deverá ser seja o mais antigo).

groupbyagrupa apenas pelo item (via operator.itemgetter). A função auxiliar, chamada uma vez por agrupamento durante o maxcálculo, recebe e descompacta internamente um grupo - uma tupla com dois itens em (item, iterable)que os itens do iterável também são tuplas de dois itens (item, original index)[[os itens de SL]].

Em seguida, a função auxiliar usa um loop para determinar a contagem de entradas no iterável do grupo e o índice original mínimo; retorna esses itens como "chave de qualidade" combinada, com o sinal de índice mínimo alterado para que omax itens operação considere "melhor" os itens que ocorreram anteriormente na lista original.

Este código poderia ser muito mais simples se se preocupasse um pouco menos com problemas de grande O no tempo e no espaço, por exemplo ...

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]

mesma idéia básica, apenas expressa de maneira mais simples e compacta ... mas, infelizmente, um espaço auxiliar O (N) extra (para incorporar as iteráveis ​​dos grupos às listas) e o tempo O (N ao quadrado) (para obter o L.indexitem de cada item) . Embora a otimização prematura seja a raiz de todos os males da programação, escolher deliberadamente uma abordagem O (N ao quadrado) quando uma O (N log N) disponível está apenas indo muito contra o grão da escalabilidade! -)

Finalmente, para aqueles que preferem "oneliners" à clareza e desempenho, uma versão bônus de 1 liner com nomes adequadamente mutilados :-).

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
fonte
3
Isso quebra no Python3 se sua lista tiver tipos diferentes.
AlexLordThorsen
2
groupbyrequer classificação primeiro (O (NlogN)); usar um Counter()com most_common()pode superar isso porque ele usa um heapq para encontrar o item de maior frequência (por apenas 1 item, é o tempo de O (N)). Como Counter()agora é fortemente otimizado (a contagem ocorre em um loop C), ela pode facilmente superar essa solução, mesmo para pequenas listas. Ele sopra fora da água para grandes listas.
Martijn Pieters
Somente o requisito de 'menor índice' para empates torna essa uma solução válida apenas para esse problema. Para o caso mais geral, você definitivamente deve usar a abordagem Counter.
Martijn Pieters
@ MartijnPieters Talvez você tenha perdido a parte da pergunta em que dizia que os itens podem ser laváveis.
21817
@ wim à direita e se os itens forem laváveis. O que torna os votos no set e max aproximados ainda mais incongruentes.
Martijn Pieters
442

Um one-liner mais simples:

def most_common(lst):
    return max(set(lst), key=lst.count)
newacct
fonte
24
O OP afirmou que [..] em caso de empates, o item com o índice mais baixo deve ser retornado. Este código, em geral, não atende a esse requisito.
Stephan202
2
Além disso, o OP afirmou que os elementos devem ser laváveis: os conjuntos devem conter objetos laváveis.
Eric O Lebigot
2
Além disso, esta abordagem é algoritmicamente lento (para cada elementos set(lst), toda a lista deve ser verificado de novo) ... Provavelmente rápido o suficiente para a maioria dos usos, embora ...
Eric O Lebigot
9
Você pode substituir set(lst)por lste também funcionará com elementos não-laváveis; embora mais lento.
Newacct 6/10/09
24
Isso pode parecer atraente, mas do ponto de vista algorítmico, esse é um péssimo conselho. list.count()precisa percorrer a lista na íntegra e você faz isso para cada item único da lista. Isso faz desta uma solução O (NK) (O (N ^ 2) no pior caso). O uso de Counter()apenas leva tempo O (N)!
Martijn Pieters
185

Tomando emprestado daqui , isso pode ser usado com o Python 2.7:

from collections import Counter

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

Funciona cerca de 4-6 vezes mais rápido que as soluções de Alex e é 50 vezes mais rápido que o one-liner proposto por newacct.

Para recuperar o elemento que ocorre primeiro na lista em caso de empate:

def most_common(lst):
    data = Counter(lst)
    return max(lst, key=data.get)
Alex
fonte
3
Isso pode ser útil para alguns, mas ... infelizmente, Counter é uma subclasse de dict, e o OP disse que não poderia usar dicionários (pois os itens podem não ser laváveis).
Danimal
13
Amo isso. O one-liner de @newacct acima pode ser simples, mas é executado em O (n ^ 2); isto é, onde n é o comprimento da lista. Esta solução é O (n).
BoltzmannBrain
5
Como a simplicidade e a velocidade ... talvez não seja o ideal para OP. Mas combina comigo!
Thom
não retorna o item indexado mais baixo. most_common retorna uma lista não ordenada, e grabbing (1) apenas retorna o que quiser.
AgentBawls
@AgentBawls: most_commoné classificado por contagem, não desordenado. Dito isto, não escolherá o primeiro elemento em caso de empate; Adicionei outra maneira de usar o contador que escolhe o primeiro elemento.
User2357112 suporta Monica
58

O que você deseja é conhecido nas estatísticas como mode, e o Python, é claro, possui uma função interna para fazer exatamente isso por você:

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

Observe que, se não houver um "elemento mais comum", como os casos em que os dois primeiros estão empatados , isso aumentará StatisticsError, porque estatisticamente falando, não há modo nesse caso.

Luiz Berti
fonte
8
este não satisfaz a exigência do OP do que para voltar quando há mais de um valor mais comum - um statistics.StatisticsError é levantada
Keith Salão
5
Ops, não cumpri o requisito ao lê-lo. Ainda acredito que essa resposta tem valor, como ninguém sugeriu nesta pergunta, e é uma boa solução para o problema para pessoas com requisitos menos restritivos. Este é um dos melhores resultados para "item de comum mais na lista python"
Luiz Berti
1
Nesse caso, use a função mode nos pandas DataFrames.
Elmex80s
1
Voto positivo, este deve ser maior. E não é tão difícil de satisfazer a exigência do OP com simples try-excepto (ver o meu stackoverflow.com/a/52952300/6646912 )
Krassowski
1
@BreakBadSP, sua resposta usa mais memória por causa do adicional sete é plausível O(n^3).
Luiz Berti
9

Se eles não forem laváveis, você pode classificá-los e fazer um loop único sobre o resultado contando os itens (itens idênticos estarão próximos um do outro). Mas pode ser mais rápido torná-los laváveis ​​e usar um ditado.

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ý
fonte
Aqui está uma maneira mais simples ideone.com/Nq81vf , comparando com Alex Counter()solução
Miguel
6

Esta é uma solução 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

(invertido é usado para garantir que ele retorne o item de índice mais baixo)

ThisIsMeMoony
fonte
5

Classifique uma cópia da lista e encontre a execução mais longa. Você pode decorar a lista antes de classificá-la com o índice de cada elemento e, em seguida, escolher a execução que começa com o índice mais baixo em caso de empate.

Boojum
fonte
Os itens podem não ser comparáveis.
Pawel Furmaniak
5

Sem o requisito sobre o índice mais baixo, você pode usar collections.Counterpara isso:

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'
O padrinho
fonte
Fácil e rápido. Você é meu padrinho 😏✌
chainstair
esta resposta precisa de mais upvotes já que aborda a tarefa geral de contagem de ocorrências de elementos em uma lista usando um módulo padrão e 2 linhas de código
pcko1
4

Uma linha:

def most_common (lst):
    return max(((item, lst.count(item)) for item in set(lst)), key=lambda a: a[1])[0]
willurd
fonte
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
fonte
3

Solução simples de uma linha

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

Ele retornará o elemento mais frequente com sua frequência.

Shivam Agrawal
fonte
2

Você provavelmente não precisa mais disso, mas foi o que fiz para um problema semelhante. (Parece mais longo do que é por causa dos comentários.)

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
fonte
1
você poderia usar contra [artigo] = counter.get (item, 0) + 1 para substituir o try / exceto parte
Xueyu
1

Com base na resposta de Luiz , mas satisfazendo a condição " em caso de empates, o item com o menor índice deve ser retornado ":

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

Exemplo:

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

Aqui:

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

Tenho a vaga sensação de que existe um método em algum lugar da biblioteca padrão que fornecerá a contagem de cada elemento, mas não consigo encontrá-lo.

Lennart Regebro
fonte
3
'max' é um método. Você mudaria o nome da variável?
Pratik Deoghare 5/10/09
1
Observe que set () também requer itens hasháveis, pois a solução não funcionaria nesse caso.
Lukáš Lalinský 5/10/09
Espere, eu senti falta daquela parte de não ser lavável. Mas se os objetos tiverem igualdade, deve ser fácil torná-los laváveis.
Lennart Regebro 05/10/09
0

Esta é a solução lenta óbvia (O (n ^ 2)) se nem a classificação nem o hash forem possíveis, mas a comparação de igualdade ( ==) estiver disponível:

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]

Porém, tornar seus itens passíveis de hash ou classificáveis ​​(conforme recomendado por outras respostas) quase sempre tornaria mais rápido a localização do elemento mais comum se o tamanho da sua lista (n) fosse grande. O (n) em média com hash e O (n * log (n)) na pior das hipóteses para classificação.

pts
fonte
Para o downvoter: o que há de errado com esta resposta? Alguma das outras respostas fornece uma solução quando nem a classificação nem o hash são viáveis?
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
fonte
Isso tem uma característica de desempenho terrível quando n é grande e o número de elementos únicos também é grande: O (n) para a conversão em um conjunto e O (m * n) = O (n ^ 2) para a contagem (onde m é o número de únicos). Classificar e caminhar é O (n log n) para a classificação e 0 (n) para a caminhada.
jmucchiello
1
Sim você está certo. Agora eu sei que esta é uma solução terrível e por quê. Obrigado por comentar!! :-)
Pratik Deoghare 5/10/09
0

Eu precisava fazer isso em um programa recente. Eu admito, não consegui entender a resposta de Alex, então foi assim que acabei.

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

Eu cronometrei com a solução de Alex e é cerca de 10 a 15% mais rápida para listas curtas, mas uma vez que você ultrapassa 100 elementos ou mais (testado até 200000), é cerca de 20% mais lento.

pauleohare
fonte
-1

Olá, esta é uma solução muito simples com grandes 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

Onde numere o elemento da lista que se repete na maioria das vezes

Cena
fonte
-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
fonte
todas as outras respostas. você gostaria que eu os vinculasse?
12 rhombi na grade sem cantos 21/02
-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
fonte
6
Por favor, forneça algumas informações sobre o seu código, apenas postar código não é uma resposta completa
jhhoff02
1
Existe alguma razão para alguém usar isso nas outras 15 respostas?
Todos os trabalhadores são essenciais
-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
fonte