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'
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]
Un one-liner plus simple:
def most_common(lst):
return max(set(lst), key=lst.count)
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)
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.
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
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)
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é.
Un one-liner:
def most_common (lst):
return max(((item, lst.count(item)) for item in set(lst)), key=lambda a: a[1])[0]
# 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'
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
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.
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
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
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.
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.
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.
>>> 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'