web-dev-qa-db-fra.com

Trouver l'élément le plus commun dans une liste

Quel est un moyen efficace de rechercher l'élément le plus commun dans une liste Python?

Les éléments de ma liste ne sont pas nécessairement lavables, vous ne pouvez donc pas utiliser de dictionnaire. De plus, en cas de tirage au sort, l'élément avec l'indice le plus bas doit être renvoyé. Exemple:

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

Avec autant de solutions proposées, je suis étonné que personne n'ait proposé ce que je considérerais comme une solution évidente (pour des éléments non-lavables, mais comparables) - [itertools.groupby] [1]. itertools offre 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érons 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 déclarations print peuvent être supprimées pour mieux voir la machine en action; par exemple, avec imprime sans commentaire:

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, SL est une liste de paires, chaque paire étant un élément suivi de l'index de l'élément dans la liste d'origine (pour implémenter la condition de clé que, 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).

groupby groupe uniquement par élément (via operator.itemgetter). La fonction auxiliaire, appelée une fois par groupe pendant le calcul max, 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 n-uplets à deux éléments, (item, original index) [[les éléments de SL]].

Ensuite, la fonction auxiliaire utilise une boucle pour déterminer le nombre d'entrées dans l'itérable du groupe, et l'index d'origine minimal; il renvoie ceux-ci sous forme de "clé de qualité" combinée, avec le signe d'index min modifié, de sorte que l'opération max tient compte "mieux" des éléments apparus plus tôt dans la liste d'origine.

Ce code pourrait être beaucoup plus simple s'il inquiétait un peu moins de problèmes de Big-O dans le temps et dans l’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, exprimée simplement plus simplement et de manière compacte ... mais, hélas, un espace supplémentaire O(N) supplémentaire (pour incorporer les groupes aux listes itérables) et O (N carré) récupérez le L.index de chaque objet). Bien que l’optimisation prématurée soit la racine de tous les maux de la programmation, choisir délibérément une approche O (N carré) lorsqu’une solution O (N log N) est disponible va trop loin dans le sens de l’évolutivité! -)

Enfin, pour ceux qui préfèrent les "oneliners" à la clarté et aux performances, une version bonus à une ligne 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]
92
Alex Martelli

Un one-liner plus simple:

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

Emprunter de 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 environ 4 à 6 fois plus rapidement que les solutions d'Alex et 50 fois plus rapidement 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)
163
Alex

Ce que vous voulez est connu sous le nom de mode dans les statistiques, et Python a bien entendu une fonction intégrée qui le fait exactement 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" tel que les cas où les deux premiers sont liés, cela relèvera StatisticsError, car statistiquement, il n'y a pas mode dans ce cas.

51
Luiz Berti

Si elles ne sont pas haschiables, vous pouvez les trier et faire une boucle unique sur le résultat en comptant les éléments (les éléments identiques seront côte à côte). Mais il serait peut-être plus rapide de les rendre utilisables 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
9

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

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

6
ThisIsMeMoony

Triez une copie de la liste et trouvez le plus long terme. Vous pouvez décorer la liste avant de la trier avec l'index de chaque élément, puis choisir l'exécution qui commence par l'index le plus bas en cas d'égalité.

5
Boojum

Un one-liner:

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

Vous n’avez probablement plus besoin de ça, mais c’est ce que j’ai fait pour un problème similaire. (Cela semble plus long que ce n'est à 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
3
Ed Holden

Solution simple en une ligne

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

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

2
Shivam Agrawal

Construire sur réponse de Luiz , mais en satisfaisant la condition " en cas de tirage, l'élément avec l'indice le plus bas doit être renvoyé ":

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
2
krassowski

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

1
Scene

Je devais le faire dans un programme récent. Je vais l'admettre, je ne comprenais pas la réponse d'Alex, alors c'est 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 comparé à 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), il est environ 20% plus lent.

0
user3596355

C'est la solution lente évidente (O (n ^ 2)) si ni le tri ni le hachage ne sont réalisables, mais que 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]

Toutefois, rendre vos éléments traitables ou traitables (comme recommandé par d’autres réponses) rendrait presque toujours plus rapide la recherche de l’élément le plus courant si la longueur de votre liste (n) est importante. O(n) en moyenne avec hachage, et O (n * log (n)) au pire pour le tri.

0
pts

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.

0
Lennart Regebro
>>> 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'
0
Pratik Deoghare