Trouvez l'élément le plus courant dans une liste


174

Quel est le moyen efficace de trouver l'élément le plus courant dans une liste Python?

Les éléments de ma liste peuvent ne pas être hachables et je ne peux donc pas utiliser de dictionnaire. Aussi en cas de tirages, l'élément avec l'indice le plus bas doit être retourné. Exemple:

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

2
Si les éléments de la liste ne sont pas hachables, comment déterminez-vous s'ils sont «égaux»? La perte d'efficacité dans la détermination de l'égalité pour les éléments non hachables annulerait probablement toute efficacité que vous espérez gagner avec un bon algorithme :)
HS.

3
Je pense qu'il veut dire que les éléments peuvent être mutables et donc pas éligibles pour être des clés dans un hashmap ...
fortran

1
oui c'est ce que je voulais dire - parfois il contiendra des listes
hoju


Réponses:


96

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]. itertoolsoffre 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 printdé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, SLest 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).

groupbygroupes par élément uniquement (via operator.itemgetter). La fonction auxiliaire, appelée une fois par regroupement pendant le maxcalcul, 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' maxopé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.indexde 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]

3
Cela casse sur Python3 si votre liste a différents types.
AlexLordThorsen

2
groupbynécessite un tri en premier (O (NlogN)); utiliser un Counter()avec most_common()peut battre cela car il utilise un heapq pour trouver l'élément de fréquence la plus élevée (pour seulement 1 élément, c'est le temps O (N)). Comme Counter()maintenant est fortement optimisé (le comptage a lieu dans une boucle C), il peut facilement battre cette solution même pour les petites listes. Il le souffle hors de l'eau pour les grandes listes.
Martijn Pieters

Seule l'exigence de «l'indice le plus bas» pour les égalités en fait une solution valable uniquement pour ce problème. Pour le cas plus général, vous devez absolument utiliser l'approche Counter.
Martijn Pieters

@MartijnPieters Vous avez peut-être manqué la partie de la question où il est dit que les éléments peuvent être indéchiffrables.
wim

@wim right, et si les éléments sont indéchiffrables. Ce qui rend les votes sur le plateau et l'approche max d'autant plus incongrus.
Martijn Pieters

442

Un one-liner plus simple:

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

24
Le PO a déclaré que [..] en cas de tirages, l'élément avec l'indice le plus bas devrait être retourné. Ce code ne répond généralement pas à cette exigence.
Stephan202

2
De plus, l'OP a déclaré que les éléments doivent être hachables: les ensembles doivent contenir des objets hachables.
Eric O Lebigot

2
De plus, cette approche est algorithmiquement lente (pour chaque élément de set(lst), la liste entière doit être vérifiée à nouveau)… Probablement assez rapide pour la plupart des utilisations, cependant…
Eric O Lebigot

9
Vous pouvez remplacer set(lst)par lstet cela fonctionnera également avec des éléments non hachables; quoique plus lent.
newacct

24
Cela peut sembler attrayant, mais d'un point de vue algorithmique, c'est un conseil terrible. list.count()doit parcourir la liste dans son intégralité , et vous le faites pour chaque élément unique de la liste. Cela en fait une solution O (NK) (O (N ^ 2) dans le pire des cas). L'utilisation de a Counter()ne prend qu'un temps O (N)!
Martijn Pieters

185

Emprunt à partir d' ici , cela peut être utilisé avec Python 2.7:

from collections import Counter

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

Fonctionne 4 à 6 fois plus vite que les solutions d'Alex et est 50 fois plus rapide que le one-liner proposé par newacct.

Pour récupérer l'élément qui apparaît en premier dans la liste en cas d'égalité:

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

3
Cela pourrait être utile à certains, mais ... malheureusement, Counter est une sous-classe de dict, et l'OP a dit qu'il ne pouvait pas utiliser de dictionnaires (car les éléments peuvent ne pas être hachables).
Danimal

13
Aime ça. Le one-liner de @newacct ci-dessus peut être simple, mais il s'exécute en O (n ^ 2); c'est-à-dire où n est la longueur de la liste. Cette solution est O (n).
BoltzmannBrain

5
Comme la simplicité et la vitesse ... peut-être pas idéal pour OP. Mais me convient très bien!
Thom

ne renvoie pas l'élément indexé le plus bas. most_common renvoie une liste non ordonnée et grabbing (1) renvoie simplement ce qu'il veut.
AgentBawls

@AgentBawls: most_commonest trié par nombre et non par ordre. Cela dit, il ne choisira pas le premier élément en cas d'égalité; J'ai ajouté une autre façon d'utiliser le compteur qui sélectionne le premier élément.
user2357112 prend en charge Monica

58

Ce que vous voulez est connu dans les statistiques sous le nom de mode, et Python a bien sûr une fonction intégrée pour faire exactement cela pour vous:

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

Notez que s'il n'y a pas d '«élément le plus commun» comme les cas où les deux premiers sont à égalité , cela augmentera StatisticsError, car statistiquement parlant, il n'y a pas de mode dans ce cas.


8
cela ne satisfait pas à ce que l'exigence de l'OP de revenir quand il y a plus d'une valeur la plus commune - un statistics.StatisticsError est élevé
Keith Hall

5
Oups, j'ai manqué l'exigence lors de la lecture. Je crois toujours que cette réponse a de la valeur, comme personne ne l’a suggéré dans cette question, et c’est une bonne solution au problème pour les personnes ayant les exigences les moins restrictives. C'est l'un des meilleurs résultats pour "l'élément le plus courant dans la liste python"
Luiz Berti

1
Dans ce cas, utilisez la fonction mode dans pandas DataFrames.
Elmex80s

1
Up-vote, celui-ci devrait être plus élevé. Et ce n'est pas si difficile de satisfaire les exigences de l'OP avec un simple essai sauf (voir mon stackoverflow.com/a/52952300/6646912 )
krassowski

1
@BreakBadSP votre réponse utilise plus de mémoire en raison du supplément set, et est plausible O(n^3).
Luiz Berti

9

S'ils ne sont pas hachables, vous pouvez les trier et faire une seule boucle sur le résultat en comptant les éléments (les éléments identiques seront côte à côte). Mais il peut être plus rapide de les rendre hachables et d'utiliser 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

Voici une manière plus simple ideone.com/Nq81vf , comparée à la Counter()solution d'Alex
Miguel

6

Ceci est une solution 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

(inversé est utilisé pour s'assurer qu'il renvoie l'élément d'index le plus bas)


6

Sans l'exigence relative à l'indice le plus bas, vous pouvez utiliser collections.Counterpour cela:

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 et rapide. Vous r mon parrain 😏✌
chainstair

1
cette réponse a besoin de plus de votes positifs car elle aborde la tâche générale de compter les occurrences d'éléments dans une liste à l'aide d'un module standard et de 2 lignes de code
pcko1

5

Triez une copie de la liste et recherchez la plus longue série. Vous pouvez décorer la liste avant de la trier avec l'index de chaque élément, puis choisir la séquence qui commence par l'index le plus bas en cas d'égalité.


Les éléments peuvent ne pas être comparables.
Pawel Furmaniak

4

Un one-liner:

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

Solution simple en une ligne

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

Il renverra l'élément le plus fréquent avec sa fréquence.


2

Vous n'en avez probablement plus besoin, mais c'est ce que j'ai fait pour un problème similaire. (Il a l'air plus long qu'à cause des commentaires.)

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
vous pouvez utiliser counter [item] = counter.get (item, 0) + 1 pour remplacer la partie try / except
XueYu

1

En s'appuyant sur la réponse de Luiz , mais en remplissant la condition " en cas de tirage au sort, l'élément avec l'indice le plus bas doit être retourné ":

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

Exemple:

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

0

Ici:

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

J'ai un vague sentiment qu'il existe une méthode quelque part dans la bibliothèque standard qui vous donnera le décompte de chaque élément, mais je ne la trouve pas.


3
«max» est une méthode. Souhaitez-vous changer le nom de la variable?
Pratik Deoghare

1
Notez que set () nécessite également des éléments hachables, car la solution ne fonctionnerait pas dans ce cas.
Lukáš Lalinský

Attendez, j'ai raté cette partie de ne pas être hashable. Mais si les objets ont l'égalité, il devrait être facile de les rendre hachables.
Lennart Regebro

0

C'est la solution lente évidente (O (n ^ 2)) si ni le tri ni le hachage ne sont possibles, mais la comparaison d'égalité ( ==) est disponible:

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]

Mais rendre vos éléments hachables ou triables (comme recommandé par d'autres réponses) permettrait presque toujours de trouver l'élément le plus courant plus rapidement si la longueur de votre liste (n) est grande. O (n) en moyenne avec hachage, et O (n * log (n)) au pire pour le tri.


Pour le downvoter: quel est le problème avec cette réponse? L'une des autres réponses offre-t-elle une solution lorsque ni le tri ni le hachage ne sont possibles?
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'

Cela a des performances terribles lorsque n est grand et que le nombre d'éléments uniques est également grand: O (n) pour la conversion en un ensemble et O (m * n) = O (n ^ 2) pour le nombre (où m est le nombre d'uniques). Trier et marcher est O (n log n) pour le tri et 0 (n) pour la marche.
jmucchiello

1
Oui tu as raison. Maintenant, je sais que c'est une solution terrible et pourquoi. Merci pour le commentaire!! :-)
Pratik Deoghare

0

J'avais besoin de le faire dans un programme récent. Je l'admets, je n'ai pas pu comprendre la réponse d'Alex, c'est donc ce avec quoi j'ai fini.

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

Je l'ai chronométré par rapport à la solution d'Alex et c'est environ 10 à 15% plus rapide pour les listes courtes, mais une fois que vous dépassez 100 éléments ou plus (testé jusqu'à 200 000), c'est environ 20% plus lent.


-1

Salut c'est une solution très simple avec gros 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

Où numéroter l'élément de la liste qui se répète la plupart du temps


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


toutes les autres réponses. souhaitez-vous que je les lie?
12 losanges dans la grille sans coins le

-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
Veuillez fournir des informations sur votre code, le simple fait de poster le code n'est pas une réponse complète
jhhoff02

1
Y a-t-il une raison pour laquelle quelqu'un devrait utiliser ceci sur les 15 autres réponses?
Tous les travailleurs sont essentiels

-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)
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.