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


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.