Listedeki en yaygın öğeyi bulma


174

Python listesindeki en yaygın öğeyi bulmanın etkili bir yolu nedir?

Liste öğelerim yıkanamayabilir, bu nedenle sözlük kullanamazsınız. Ayrıca çekiliş durumunda en düşük endekse sahip ürün iade edilmelidir. Misal:

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

2
Listedeki öğeler yıkanamazsa, 'eşit' olduklarını nasıl belirlersiniz? Yıkanamayan ürünler için eşitliği belirlemedeki verimlilik kaybı, muhtemelen iyi bir algoritma ile kazanmayı umduğunuz herhangi bir verimliliği olumsuz yönde etkileyecektir :)
HS.

3
Bence öğeleri değişebilir ve böylece bir hashmap anahtarları için uygun değildir anlamına gelir ...
fortran

1
evet demek istediğim buydu - bazen listeleri içerecek
hoju


Yanıtlar:


96

Bu kadar çok çözüm önerildiğinde, kimsenin bariz olanı düşündüğüm şeyi önermediğine şaşırdım (yıkanamaz ancak karşılaştırılabilir öğeler için) - [ itertools.groupby] [1]. itertoolshızlı, yeniden kullanılabilir işlevsellik sunar ve bazı zor mantığı iyi test edilmiş standart kütüphane bileşenlerine devredebilmenizi sağlar. Örneğin şunu düşünün:

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]

Bu elbette daha kısaca yazılabilir, ama maksimum netliği hedefliyorum. printMakineyi çalışırken daha iyi görmek için bu iki ifade önerilmemektedir; örneğin, ile baskı uncommented:

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

yayar:

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

Gördüğünüz gibi, SL , çiftlerin bir listesidir, her bir öğe bir öğeyi ve ardından orijinal listede öğenin dizinini eşleştirir (aynı en yüksek sayıya sahip "en yaygın" öğelerin> 1 olması durumunda, sonucun en eski olanı ol).

groupby yalnızca öğeye göre gruplandırır (üzerinden operator.itemgetter ). maxHesaplama sırasında gruplama başına bir kez çağrılan yardımcı işlev, bir grubu alır ve dahili olarak açar - (item, iterable)yinelenebilir öğelerin de iki öğeli tuples olduğu iki öğeli bir demet (item, original index)[[öğeleri SL]].

Daha sonra yardımcı fonksiyon, grubun yinelenebilir girişindeki giriş sayısını ve minimum orijinal dizini belirlemek için bir döngü kullanır ; bunları birleştirilmiş "kalite anahtarı" olarak döndürür, min dizini işareti değiştirilir, böylece maxişlem orijinal listede daha önce gerçekleşen öğeleri "daha iyi" olarak değerlendirir.

Bir endişe varsa bu kod çok daha basit olabilir , zaman ve mekandaki büyük O sorunları hakkında biraz daha az , örneğin ...

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]

aynı temel fikir, sadece daha basit ve kompakt bir şekilde ifade edildi ... ama, ne yazık ki, ekstra bir O (N) yardımcı alan (grupların yinelenebilirlerini listelere yerleştirmek için) ve O (N kare) süresi ( L.indexher öğeden almak için ) . Erken optimizasyon programlamadaki tüm kötülüklerin kökü olsa da, bir O (N log N) mevcut olduğunda kasıtlı olarak bir O (N kare) yaklaşımı seçmek, ölçeklenebilirlik tanesine karşı çok fazla gider! -)

Son olarak, netlik ve performans için "oneliners" i tercih edenler için, uygun şekilde karıştırılmış isimlere sahip bonus 1-liner versiyonu :-).

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
Listenizde farklı türler varsa bu Python3'te kesilir.
AlexLordThorsen

2
groupbyönce sıralama gerektirir (O ​​(NlogN)); bir Counter()ile most_common()kullanmak, en yüksek frekans öğesini bulmak için bir yığın kullandığından bunu yenebilir (sadece 1 öğe için, O (N) zamanı). Gibi Counter()şimdi ağır (sayım C döngüde gerçekleşir) optimize edilmiştir, kolayca hatta küçük listeler için bu çözümü yenebilir. Büyük listeler için sudan çıkarır.
Martijn Pieters

Bağlar için sadece 'en düşük endeks' gereksinimi, bunu sadece bu sorun için geçerli bir çözüm haline getirir. Daha genel bir durum için kesinlikle Counter yaklaşımını kullanmalısınız.
Martijn Pieters

@MartijnPieters Belki de sorunun öğelerin sarsılmaz olabileceğini söylediği kısmını kaçırdınız.
wim

@ wim sağ ve eğer öğelerin tıraşlanamazsa. Bu, set ve max yaklaşımındaki oyları daha tutarsız hale getirir.
Martijn Pieters

442

Daha basit bir astar:

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

24
OP , en düşük endekse sahip kalem çekildiğinde [..] olduğunu belirtmiştir . Bu kod genel olarak bu şartı karşılamaz.
Stephan202

2
Ayrıca OP elemanların yıkanabilir olması gerektiğini belirtti: setler yıkanabilir nesneler içermelidir.
Eric O Lebigot

2
Ayrıca, bu yaklaşım algoritmik olarak yavaştır (içindeki her öğe set(lst)için tüm liste tekrar kontrol edilmelidir)… Muhtemelen çoğu kullanım için yeterince hızlı ...
Eric O Lebigot

9
Sen yerini alabilir set(lst)ile lstve çok olmayan hashable unsurlarla çalışacaktır; yavaş da olsa.
newacct

24
Bu çekici görünebilir, ancak algoritmik bir bakış açısından bu korkunç bir tavsiye. list.count()listeyi tam olarak geçmesi gerekir ve bunu listedeki her benzersiz öğe için yaparsınız . Bu, bunu bir O (NK) çözeltisi yapar (en kötü durumda O (N ^ 2)). Bir kullanılması Counter()yalnızca sürer O (K) zamanı!
Martijn Pieters

185

Buradan ödünç alma , bu Python 2.7 ile kullanılabilir:

from collections import Counter

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

Alex'in çözümlerinden 4-6 kat daha hızlı çalışır ve newacct tarafından önerilen tek astardan 50 kat daha hızlıdır.

Bağ olması durumunda listede ilk ortaya çıkan elemanı almak için:

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

3
Bu bazıları için yararlı olabilir ama ... ne yazık ki Counter bir dict alt sınıfı ve OP sözlükleri kullanamayacağını söyledi (öğeler yıkanamayabilir).
Danimal

13
Bunu sev. Yukarıdaki @newacct tarafından sağlanan tek astar basit olabilir, ancak O (n ^ 2) olarak çalışır; yani n listenin uzunluğudur. Bu çözelti O (n) dir.
BoltzmannBrain

5
Basitlik ve hız gibi ... belki OP için ideal olmayabilir. Ama bana çok yakışıyor!
Thom

en düşük endekslenmiş öğeyi döndürmez. most_common sırasız bir liste döndürür ve kapma (1) istediği her şeyi döndürür.
AgentBawls

@AgentBawls: sırasına most_commongöre değil, sayıma göre sıralanır. Bununla birlikte, bağlar durumunda ilk elemanı seçmeyecektir; İlk öğeyi seçen sayacı kullanmak için başka bir yol ekledim.
user2357112 Monica

58

İstediğiniz şey istatistiklerde mod olarak bilinir ve elbette Python'un sizin için tam olarak yapması için yerleşik bir işlevi vardır:

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

İlk ikisinin bağlandığı durumlar gibi "en yaygın öğe" yoksa , bunun artacağını unutmayın StatisticsError, çünkü istatistiksel olarak konuşursak, bu durumda mod yoktur .


8
Bu, birden fazla en yaygın değer varken dönmek için ne OP'ın gereksinimi karşılamak değil - bir statistics.StatisticsError yükseltilir
Keith Hall

5
Hata, okurken gereksinimi kaçırdı. Yine de, bu soruda kimse bunu önermediği için bu cevabın değer taşıdığına inanıyorum ve en az kısıtlayıcı gereksinimleri olan insanlar için sorun için iyi bir çözüm. Bu, "liste python'daki en yaygın öğe" için en iyi sonuçlardan biridir
Luiz Berti

1
Bu durumda panda DataFrames'da mod işlevini kullanın.
Elmex80s

1
Yukarı oy, bu daha yüksek olmalı. Ve OP'nin ihtiyacını basit deneme dışında karşılamak zor değil (bkz. Stackoverflow.com/a/52952300/6646912 )
krassowski

1
@BreakBadSP cevabınız ek yüzünden daha fazla bellek kullanıyor setve akla yatkın O(n^3).
Luiz Berti

9

Yıkanabilir değilse, bunları sıralayabilir ve öğelerin sayılmasıyla sonuç üzerinde tek bir döngü yapabilirsiniz (aynı öğeler yan yana olacaktır). Ancak onları yıkanabilir hale getirmek ve bir diksiyon kullanmak daha hızlı olabilir.

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

İşte ideone.com/Nq81vf , Alex'in Counter()çözümü ile karşılaştırıldığında
Miguel

6

Bu bir O (n) çözeltisidir.

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

(en düşük dizin öğesini döndürdüğünden emin olmak için tersine çevrilir)


6

En düşük endeks ile ilgili gereksinim olmadan, aşağıdakiler için kullanabilirsiniz 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'

Kolay ve hızlı. Sen benim Godfather 😏✌ r
chainstair

1
Bu yanıt, standart bir modül ve 2 kod satırı kullanarak bir listedeki öğe oluşumlarını sayma genel görevini ele aldığı için daha fazla oylamaya ihtiyaç duyar
pcko1

5

Listenin bir kopyasını sıralayın ve en uzun koşuyu bulun. Her öğenin diziniyle sıralamadan önce listeyi dekore edebilir ve ardından bir bağlantı durumunda en düşük dizinle başlayan çalıştırmayı seçebilirsiniz.


Öğeler karşılaştırılamayabilir.
Pawel Furmaniak

4

Bir astar:

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

Basit tek satırlık çözüm

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

Sıklığı ile en sık kullanılan elemanı döndürür.


2

Muhtemelen buna artık ihtiyacınız yok, ama benzer bir sorun için yaptığım şey bu. (Yorumlar nedeniyle olduğundan daha uzun görünüyor.)

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
try / hariç parçayı değiştirmek için counter [item] = counter.get (item, 0) + 1 kullanabilirsiniz
XueYu 28:06

1

Luiz'in cevabına dayanarak , " en düşük endekse sahip ürün çekilmesi durumunda iade edilmelidir " koşulunu yerine getirmek:

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

Misal:

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

0

Buraya:

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

Standart kütüphanede bir yerde size her elementin sayısını verecek bir yöntem olduğunu belirten bir his var, ama bulamıyorum.


3
'max' bir yöntemdir. Değişkenin adını değiştirir misiniz?
Pratik Deoghare

1
Set () öğesinin de yıkanabilir öğeler gerektirdiğini unutmayın, bu durumda çözüm işe yaramaz.
Lukáš Lalinský

Bekle, yıkanamaz olmanın bir kısmını kaçırdım. Fakat nesnelerin eşitliği varsa, onları yıkanabilir kılmak kolay olmalıdır.
Lennart Regebro

0

Ne sıralama ne de hashlama yapılabilirse, ancak eşitlik karşılaştırması ( ==) mevcutsa , bu açık yavaş çözümdür (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]

Ancak, öğelerinizin yıkanabilir veya sıralanabilir hale getirilmesi (diğer yanıtlar tarafından önerildiği gibi), listenizdeki uzunluk (n) büyükse, hemen hemen her zaman en yaygın öğeyi bulmayı daha hızlı hale getirir. Ortalama karma (O (n)) ve sıralama için en kötü O (n * log (n)).


Downvoter'a: Bu cevapta sorun nedir? Ne sıralama ne de hashlama mümkün olmadığında diğer cevaplardan herhangi biri çözüm sağlıyor mu?
Puan

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'

Bu, n büyük olduğunda ve benzersiz elemanların sayısı da büyük olduğunda korkunç bir performans özelliğine sahiptir: Bir kümeye dönüşüm için O (n) ve sayım için O (n * = = O (n ^ 2) (burada m benzersiz sayısıdır). Sıralama ve yürüyüş sıralama için O (n log n) ve yürüyüş için 0 (n) 'dir.
jmucchiello

1
Evet haklısın. Şimdi bunun korkunç bir çözüm olduğunu biliyorum ve neden. Yorum için teşekkürler!! :-)
Pratik Deoghare

0

Bunu yeni bir programda yapmam gerekiyordu. İtiraf edeceğim, Alex'in cevabını anlayamadım, bu yüzden sonuçlandım.

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

Alex'in çözümüne karşı zamanladım ve kısa listeler için yaklaşık% 10-15 daha hızlı, ancak 100 öğeyi veya daha fazlasını (200000'e kadar test edildi) geçtikten sonra yaklaşık% 20 daha yavaş.


-1

Merhaba bu büyük O (n) ile çok basit bir çözüm

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

Listede çoğu zaman yinelenen öğeyi numaralandırmak


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


diğer tüm cevaplar. onları bağlamamı ister misin?
12 köşeli ızgarada köşe yok köşeler

-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
Lütfen kodunuzla ilgili bazı bilgiler sağlayın, sadece kod göndermek tam bir cevap değil
jhhoff02

1
Birinin bunu diğer 15 cevapta kullanması için bir neden var mı?
Tüm İşçiler

-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)
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.