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


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.