Знайдіть найпоширеніший елемент у списку


174

Який ефективний спосіб знайти найпоширеніший елемент у списку Python?

Елементи мого списку можуть бути не доступними, тому не можна користуватися словником. Також у випадку розіграшу повертається елемент з найнижчим показником. Приклад:

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

2
Якщо елементи зі списку не підлягають доступності, як би ви визначили, коли вони "рівні"? Втрата ефективності при визначенні рівності для непридатних елементів, ймовірно, заперечує будь-яку ефективність, яку ви сподіваєтеся отримати за допомогою хорошого алгоритму :)
HS.

3
Я думаю , що він мав в виду , що елементи можуть бути змінним і , таким чином , НЕ elegible бути ключів в HashMap ...
Фортран

1
Так, я це мав на увазі - іноді він буде містити списки
ходжу


Відповіді:


96

Маючи так багато запропонованих рішень, я вражений, що ніхто не запропонував те, що я вважав би очевидним (для непридатних, але порівняних елементів) - [ itertools.groupby] [1]. itertoolsпропонує швидку функціональність для багаторазового використання та дозволяє делегувати деяку складну логіку добре перевіреним стандартним компонентам бібліотеки. Розглянемо для прикладу:

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]

Це, звичайно, можна написати більш стисло, але я прагну до максимальної ясності. Два printтвердження можна коментувати, щоб краще бачити механізм дії; наприклад, з відбитками без коментарів:

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

випускає:

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

Як бачите, SLце список пар, кожна пара - предмет, за яким іде індекс елемента в початковому списку (щоб реалізувати ключову умову, що, якщо "найпоширеніші" елементи з однаковим найбільшим числом> 1, результат повинен бути бути найбільш ранньою).

groupbyгрупи лише за пунктом (через operator.itemgetter). Допоміжна функція, що викликається один раз на групування під час maxобчислення, отримує та внутрішньо розпаковує групу - кортеж з двома елементами, (item, iterable)де елементи ітерабеля також є двоярусними кортежами, (item, original index)[[пункти SL]].

Тоді допоміжна функція використовує цикл, щоб визначити як кількість записів у групі ітерабелів, так і мінімальний вихідний індекс; він повертає ті, що є комбінованим "ключем якості", зі знаком мінімального індексу, зміненим, тому maxоперація вважатиме "кращими" ті елементи, які відбулися раніше в первинному списку.

Цей код може бути набагато простішим, якби він трохи менше турбував проблеми з великим O у часі та просторі, наприклад ...:

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]

така ж основна ідея, що висловлюється просто і компактно ... але, на жаль, додатковий допоміжний простір O (N) (для втілення ітерабелів груп у списки) та час (N у квадраті) для отримання L.indexкожного елемента) . Хоча передчасна оптимізація є коренем усього зла в програмуванні, свідомо вибирати підхід O (N у квадраті), коли доступний O (N log N) просто надто сильно проти зерна масштабованості! -)

Нарешті, для тих, хто віддає перевагу чіткості та продуктивності "oneliners", бонусна версія 1-лайнера з відповідними накрученими назвами :-).

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
Це припиняється на Python3, якщо ваш список має різні типи.
AlexLordThorsen

2
groupbyвимагає сортування спочатку (O (NlogN)); використовуючи Counter()з most_common()може бити , що , оскільки він використовує heapq , щоб знайти найвищий елемент частоти (для всього 1 пункт, що O (N) часу). Оскільки Counter()зараз сильно оптимізовано (підрахунок відбувається в циклі С), це рішення може легко перемогти це рішення навіть для невеликих списків. Це видуває його з води для великих списків.
Martijn Pieters

Тільки вимога "найнижчого індексу" для зв'язків робить це дійсним рішенням саме цієї проблеми. Для більш загального випадку вам неодмінно слід використовувати підхід Counter.
Martijn Pieters

@MartijnPieters Можливо, ви пропустили частину питання, де в ньому сказано, що пункти можуть бути непорушними.
Вім

@wim правильно, і якщо предмети не змінні. Що робить голоси на знімальному майданчику та максимальний підхід ще більш невідчутними.
Martijn Pieters

442

Більш простий однолінійний:

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

24
ОП заявило, що [..] у випадку розіграшу пункту з найнижчим показником слід повернути. Цей код, як правило, не відповідає цій вимозі.
Stephan202

2
Крім того, ОП заявила, що елементи повинні бути перебірливими: набори повинні містити об'єкти, що змішуються.
Ерік О Лебігот

2
Плюс цей алгоритмічно повільний підхід (для кожного елемента в set(lst)цьому списку потрібно перевірити ще раз)… Мабуть, досить швидкий для більшості застосувань, хоча…
Ерік О Лебігот

9
Ви можете замінити set(lst)з , lstі він буде працювати з не-hashable елементів теж; хоч і повільніше.
newacct

24
Це може виглядати привабливо, але з алгоритмічної точки зору це жахлива порада. list.count()має пройти список повністю , і ви робите це для кожного унікального елемента в списку. Це робить рішення O (NK) (в гіршому випадку O (N ^ 2)). Використання Counter()лише займає O (N) час!
Martijn Pieters

185

Займаючи звідси , це можна використовувати з Python 2.7:

from collections import Counter

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

Працює в 4-6 разів швидше, ніж рішення Алекса, і в 50 разів швидше, ніж однолінійний, запропонований Newacct.

Щоб отримати елемент, який з’являється першим у списку у разі зв’язків:

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

3
Це може бути корисним для деяких, але ... на жаль, лічильник - це підклас диктантів, і ОП заявила, що не може використовувати словники (оскільки елементи можуть бути не підлягають зміні).
Данімаль

13
Люблю це. Однокласник @newacct вище може бути простим, але він працює в O (n ^ 2); тобто де n - довжина списку. Це рішення O (n).
BoltzmannBrain

5
Як простота і швидкість ... можливо, не ідеально підходить для ОП. Але мені підходить чудово!
Том

не повертає найнижчий індексований елемент. most_common повертає не упорядкований список, а захоплення (1) просто повертає все, що б воно хотілося.
AgentBawls

@AgentBawls: most_commonсортується за кількістю, не має упорядкованості. При цьому, перший елемент не вибере у випадку зв’язків; Я додав ще один спосіб використання лічильника, який вибирає перший елемент.
user2357112 підтримує Моніку

58

Те, що ви хочете, відомо в статистиці як режим, і Python, звичайно, має вбудовану функцію, щоб зробити саме це для вас:

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

Зауважте, що якщо немає "найпоширенішого елемента", такого як випадки, коли два кращих пов'язані , це підвищиться StatisticsError, оскільки статистично кажучи, в цьому випадку режиму немає .


8
це не задовольняє вимогу ОП про те, що повернути, коли є більше одного найпоширенішого значення - статистики. СтатистикаError піднята
Кіт Хол

5
На жаль, пропустили вимогу під час її читання. Я все ще вважаю, що ця відповідь має значення, оскільки ніхто не запропонував її у цьому питанні, і це хороше рішення проблеми для людей з найменшими обмежувальними вимогами. Це один з найкращих результатів для "найпоширенішого елемента в списку python"
Луїз Берті

1
У цьому випадку використовуйте функцію режиму в пандах DataFrames.
Elmex80s

1
Підголошення, це повинно бути вище. І це не так уже й важко задовольнити вимогу до OP з простим примірочних , за винятком (дивіться мої stackoverflow.com/a/52952300/6646912 )
Krassowski

1
@BreakBadSP ваша відповідь використовує більше пам’яті через додаткову set, і це правдоподібно O(n^3).
Луїз Берті

9

Якщо вони не є хешируемими, ви можете їх сортувати і зробити один цикл за результатом підрахунку елементів (однакові елементи будуть поруч один з одним). Але це може бути швидше зробити їх доступними і використовувати диктант.

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

Ось простіший спосіб ideone.com/Nq81vf , порівняння з Counter()рішенням Алекса
Мігель

6

Це 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

(Зворотний використовується, щоб переконатися, що він повертає найнижчий показник)


6

Без вимоги про найнижчий індекс ви можете використовувати collections.Counterдля цього:

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'

Легко і швидко. Ти мій хрещений батько 😏✌
церемонія

1
ця відповідь потребує більше оновлень, оскільки вона вирішує загальну задачу підрахунку подій елементів у списку за допомогою стандартного модуля та 2 рядків коду
pcko1

5

Сортуйте копію списку та знайдіть найдовший пробіг. Ви можете прикрасити список, перш ніж сортувати його з індексом кожного елемента, а потім вибрати запуск, який починається з найнижчого індексу у випадку крапки.


Елементи можуть бути не порівнянні.
Pawel Furmaniak

4

Одноколісний:

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

Просте рішення на одну лінію

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

Він поверне найчастіший елемент зі своєю частотою.


2

Вам, мабуть, це вже не потрібно, але це я зробив для подібної проблеми. (Це виглядає довше, ніж це через коментарі.)

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
ви можете використовувати counter [item] = counter.get (item, 0) + 1, щоб замінити спробу / крім частини
XueYu

1

Спираючись на відповідь Луїза , але задовольняючи умову " у випадку розіграшу пункту з найнижчим показником слід повернути ":

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

Приклад:

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

0

Тут:

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

У мене розпливчасте відчуття, що десь у стандартній бібліотеці є метод, який дасть вам підрахунок кожного елемента, але я не можу його знайти.


3
'max' - метод. Чи змінили б ви назву змінної?
Pratik Deoghare

1
Зауважте, що set () також вимагає доступних елементів, щоб рішення не працювало в цьому випадку.
Лукаш Лалінськийý

Зачекайте, я пропустив ту частину, що не переймається. Але якщо об'єкти мають рівність, то їх можна легко перетворити.
Леннарт Регебро

0

Це очевидне повільне рішення (O (n ^ 2)), якщо ні сортування, ні хешування неможливо, але порівняння рівності ( ==) доступне:

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]

Але якщо зробити ваші предмети доступними для сортування чи сортування (як це рекомендують інші відповіді), майже завжди швидше буде знайти найпоширеніший елемент, якщо довжина вашого списку (n) велика. O (n) в середньому з хешуванням, і O (n * log (n)) в гіршому для сортування.


Для переслідувача: що не так у цій відповіді? Чи відповідає будь-яка з інших відповідей рішення, коли ні сортування, ні хеширование неможливо?
пт

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'

Це має жахливу ефективність, коли n велика, а кількість унікальних елементів також велика: O (n) для перетворення на множину та O (m * n) = O (n ^ 2) для кількості (де m - кількість унікальних). Сортувати та пройти - це O (n log n) для сортування та 0 (n) для прогулянки.
jmucchiello

1
Так, ти маєш рацію. Тепер я знаю, що це жахливе рішення і чому. Дякуємо за коментар !! :-)
Pratik Deoghare

0

Мені це потрібно було зробити в останній програмі. Я визнаю це, я не міг зрозуміти відповідь Алекса, так що я закінчив.

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

Я приурочив це до рішення Алекса, і це скорочується на 10-15% швидше для коротких списків, але коли ви перейдете понад 100 елементів і більше (перевірено до 200000), це приблизно на 20% повільніше.


-1

Привіт, це дуже просте рішення з великим 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

Де нумерується елемент у списку, який повторюється більшу частину часу


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


всі інші відповіді. Ви хочете, щоб я пов’язав їх?
12 ромбів у сітці без кутів

-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
Будь ласка, надайте трохи інформації про свій код, лише розміщення коду не є повною відповіддю
jhhoff02

1
Чи є причина, що хтось повинен використовувати це над 15 іншими відповідями?
Усі працівники найважливіші

-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)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.