С таким количеством предложенных решений, я удивлен, что никто не предложил то, что я считаю очевидным (для не хэшируемых, но сопоставимых элементов) - [ 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]