Найти наиболее распространенный элемент в списке


174

Какой эффективный способ найти наиболее распространенный элемент в списке Python?

Элементы моего списка не могут быть хэшируемыми, поэтому не могут использовать словарь. Также в случае розыгрышей должен быть возвращен предмет с самым низким индексом. Пример:

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

2
Если элементы в списке не являются хэшируемыми, как бы вы определили, когда они «равны»? Потеря эффективности при определении равенства для не хэшируемых элементов, вероятно, сведет на нет любую эффективность, которую вы надеетесь получить с помощью хорошего алгоритма :)
— HS.

3
Я думаю, он имеет в виду, что элементы могут быть изменяемыми и, следовательно, не изящными, чтобы быть ключами в хэш-карте ...
— Fortran

1
да, это то, что я имел в виду - иногда он будет содержать списки
— hoju


Ответы:


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)[[items of SL]].

Затем вспомогательная функция использует цикл для определения количества записей в итерируемой группе и минимального исходного индекса; он возвращает их как объединенный «ключ качества» с измененным знаком мин индекса, поэтому maxоперация будет считать «лучше» те элементы, которые встречались ранее в исходном списке.

Этот код мог бы быть намного проще, если бы он немного меньше беспокоился о проблемах больших объемов во времени и пространстве, например ...:

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) вспомогательное пространство (для включения элементов групп в списки) и O (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()with most_common()может превзойти это, поскольку он использует heapq для поиска элемента с самой высокой частотой (только для 1 элемента это время O (N)). Поскольку Counter()сейчас он сильно оптимизирован (подсчет происходит в цикле C), он может легко превзойти это решение даже для небольших списков. Это выдувает это из воды для больших списков.
— Мартин Питерс

Только требование «самого низкого индекса» для связей делает это правильным решением именно для этой проблемы. Для более общего случая вам определенно следует использовать подход Counter.
— Мартин Питерс

@MartijnPieters Возможно, вы пропустили ту часть вопроса, в которой говорится, что предметы могут быть непоправимыми.
— Вим

@ Правильно, и если предметы не подлежат уничтожению. Что делает голосование на съемочной площадке и максимальный подход еще более неуместным.
— Мартин Питерс

442

Более простой однострочник:

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

24
ОП заявил, что [...] в случае розыгрышей должен быть возвращен элемент с самым низким индексом. Этот код, как правило, не соответствует этому требованию.
— Stephan202

2
Кроме того, OP заявил, что элементы должны быть хэшируемыми: наборы должны содержать хэшируемые объекты.
— Эрик О Лебиго

2
Кроме того, этот подход является алгоритмически медленным (для каждого элемента set(lst), весь список должен быть проверен еще раз) ... Вероятно, достаточно быстро для большинства применений, хотя ...
— Эрик О Лебигот

9
Вы можете заменить set(lst)на, lstи это также будет работать с не хэш-элементами; хотя и медленнее.
— newacct

24
Это может выглядеть привлекательно, но с алгоритмической точки зрения это ужасный совет. list.count()должен пройти список полностью , и вы делаете это для каждого уникального элемента в списке. Это делает это решение O (NK) (O (N ^ 2) в худшем случае). Использование Counter()только занимает O (N) время!
— Мартин Питерс

185

Заимствуя отсюда , это может использоваться с Python 2.7:

from collections import Counter

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

Работает примерно в 4-6 раз быстрее, чем решения Alex, и в 50 раз быстрее, чем однострочный, предложенный newacct.

Чтобы получить элемент, который появляется первым в списке в случае связей:

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

3
Это может быть полезно для некоторых, но ... к сожалению Counter является подклассом dict, и ОП сказал, что он не может использовать словари (поскольку элементы могут не быть хэшируемыми).
— Данимал,

13
Люблю это. Однострочное из @newacct выше может быть простым, но оно работает в O (n ^ 2); то есть, где n - длина списка. Это решение O (n).
— BoltzmannBrain

5
Нравится простота и скорость ... может быть, не идеально для OP. Но подходит мне отлично!
— Том

не возвращает наименее проиндексированный элемент. most_common возвращает неупорядоченный список, а grabbing (1) просто возвращает все, что ему нужно.
— AgentBawls

@AgentBawls: most_commonотсортировано по количеству, а не по порядку. Тем не менее, он не выберет первый элемент в случае связей; Я добавил еще один способ использования счетчика, который выбирает первый элемент.
— user2357112 поддерживает Monica

58

То, что вы хотите, в статистике называется режимом, и, конечно, в Python есть встроенная функция, которая сделает это именно за вас:

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

Обратите внимание, что если нет «самого распространенного элемента», например, в случаях, когда два верхних элемента связаны , то это повысится StatisticsError, потому что, по статистике, в этом случае нет режима .


8
это не удовлетворяет требованию ОП о том, что возвращать, когда существует более одного наиболее распространенного значения - статистика.
— Кит Холл

5
Упс, пропустил требование при чтении. Я все еще считаю, что этот ответ имеет значение, поскольку никто не предложил его в этом вопросе, и это хорошее решение проблемы для людей с наименьшими ограничительными требованиями. Это один из лучших результатов для "самого распространенного элемента в списке python"
— Луис Берти

1
В этом случае используйте функцию режима в пандах DataFrames.
— Elmex80s

1
Голосуй вверх, этот должен быть выше. И это не так сложно, чтобы удовлетворить требования ОП с помощью простой попытки-кроме (см. Мой 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'

Легко и быстро. Ты мой крестный отец chains
— цепная лестница

1
этот ответ нуждается в большем количестве голосов, поскольку он решает общую задачу подсчета появлений элементов в списке с использованием стандартного модуля и двух строк кода
— pcko1

5

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


Предметы могут быть несопоставимы.
— Павел Фурманьяк

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, чтобы заменить
— пробную

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
«Макс» является методом. Вы бы изменили имя переменной?
— 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)) в худшем случае для сортировки.


Для downvoter: что не так с этим ответом? Предоставляет ли какой-либо другой ответ решение, когда ни сортировка, ни хеширование не осуществимы?
— Очки

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.