Temukan elemen yang paling umum dalam daftar

174

Apa cara yang efisien untuk menemukan elemen paling umum dalam daftar Python?

Item daftar saya mungkin tidak dapat hash jadi tidak bisa menggunakan kamus. Juga jika menarik item dengan indeks terendah harus dikembalikan. Contoh:

>>> most_common(['duck', 'duck', 'goose'])
'duck'
>>> most_common(['goose', 'duck', 'duck', 'goose'])
'goose'
hoju
sumber
2
Jika item dalam daftar tidak hashable, bagaimana Anda menentukan kapan mereka 'sama'? Hilangnya efisiensi dalam menentukan kesetaraan untuk item yang tidak dapat hashable mungkin akan meniadakan efisiensi yang Anda harapkan dengan algoritma yang baik :)
HS.
3
Saya pikir dia berarti bahwa barang yang bisa berubah dan dengan demikian tidak yang memenuhi syarat untuk menjadi kunci dalam hashmap ...
fortran
1
ya itulah yang saya maksud - kadang-kadang akan berisi daftar
hoju
Cara terbaik stackoverflow.com/a/50227350/7918560
BreakBadSP

Jawaban:

96

Dengan begitu banyak solusi yang diajukan, saya kagum tidak ada yang mengusulkan apa yang saya anggap sebagai solusi yang jelas (untuk elemen-elemen yang tidak dapat hash tetapi sebanding) - [ itertools.groupby] [1]. itertoolsmenawarkan fungsionalitas yang cepat dan dapat digunakan kembali, dan memungkinkan Anda mendelegasikan beberapa logika rumit ke komponen perpustakaan standar yang telah teruji dengan baik. Pertimbangkan misalnya:

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]

Ini bisa ditulis lebih ringkas, tentu saja, tapi saya bertujuan untuk kejelasan maksimal. Kedua printpernyataan tersebut dapat dibatalkan komentarnya untuk lebih melihat mesin dalam aksi; misalnya, dengan cetakan yang tidak diomortasikan:

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

memancarkan:

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

Seperti yang Anda lihat, SLadalah daftar pasangan, setiap pasangan item diikuti oleh indeks item dalam daftar asli (untuk menerapkan kondisi kunci itu, jika item "paling umum" dengan jumlah tertinggi yang sama adalah> 1, hasilnya harus menjadi yang paling awal terjadi).

groupbydikelompokkan berdasarkan item saja (via operator.itemgetter). Fungsi bantu, disebut sekali per pengelompokan selama maxperhitungan, menerima dan membongkar secara internal grup - tuple dengan dua item di (item, iterable)mana item iterable juga merupakan dua item tupel, (item, original index)[[item SL]].

Kemudian fungsi bantu menggunakan loop untuk menentukan jumlah entri dalam iterable grup, dan indeks asli minimum; itu mengembalikan mereka sebagai "kunci kualitas" gabungan, dengan tanda indeks min-diubah sehingga maxoperasi akan mempertimbangkan "lebih baik" item-item yang terjadi sebelumnya dalam daftar asli.

Kode ini bisa jauh lebih sederhana jika khawatir sedikit tentang masalah besar-O dalam ruang dan waktu, misalnya ...:

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]

ide dasar yang sama, hanya diekspresikan lebih sederhana dan padat ... tetapi, sayangnya, ruang tambahan O (N) tambahan (untuk mewujudkan iterables grup untuk daftar) dan O (N kuadrat) waktu (untuk mendapatkan L.indexsetiap item) . Sementara optimasi prematur adalah akar dari semua kejahatan dalam pemrograman, sengaja memilih pendekatan O (N kuadrat) ketika O (N log N) satu tersedia hanya berjalan terlalu banyak melawan butir skalabilitas! -)

Akhirnya, bagi mereka yang lebih suka "oneliners" untuk kejelasan dan kinerja, bonus versi 1-liner dengan nama-nama yang dicoret :-).

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]
Alex Martelli
sumber
3
Ini terpecah pada Python3 jika daftar Anda memiliki tipe yang berbeda.
AlexLordThorsen
2
groupbymembutuhkan pengurutan terlebih dahulu (O (NlogN)); menggunakan Counter()dengan most_common()dapat mengalahkan itu karena menggunakan heapq untuk menemukan item frekuensi tertinggi (hanya 1 item, itu waktu O (N)). Seperti Counter()sekarang sangat dioptimalkan (penghitungan terjadi dalam loop C), itu dapat dengan mudah mengalahkan solusi ini bahkan untuk daftar kecil. Itu mengeluarkannya dari air untuk daftar besar.
Martijn Pieters
Hanya persyaratan 'indeks terendah' ​​untuk ikatan yang menjadikan ini solusi yang valid untuk masalah ini saja. Untuk kasus yang lebih umum, Anda harus menggunakan pendekatan Counter.
Martijn Pieters
@ MartijnPieters Mungkin Anda telah melewatkan bagian dari pertanyaan di mana dikatakan bahwa item-item tersebut mungkin tidak dapat dihancurkan.
wim
@ wim benar, dan jika barang tidak dapat dihancurkan. Yang membuat suara pada set dan pendekatan max semua semakin aneh.
Martijn Pieters
442

Satu kalimat sederhana:

def most_common(lst):
    return max(set(lst), key=lst.count)
berita baru
sumber
24
OP menyatakan bahwa [..] jika menarik item dengan indeks terendah harus dikembalikan. Kode ini, secara umum, tidak memenuhi persyaratan itu.
Stephan202
2
Plus, OP menyatakan bahwa elemen harus hashable: set harus berisi objek hashable.
Eric O Lebigot
2
Plus, pendekatan ini adalah algoritma lambat (untuk setiap elemen dalam set(lst), seluruh daftar harus diperiksa lagi) ... Mungkin cukup cepat untuk sebagian besar menggunakan, meskipun ...
Eric O Lebigot
9
Anda dapat menggantinya set(lst)dengan lstdan itu akan bekerja dengan elemen yang tidak dapat di-hash juga; meskipun lebih lambat.
newacct
24
Ini mungkin terlihat menarik tetapi dari sudut pandang algoritmik ini adalah saran yang mengerikan. list.count()harus melintasi daftar secara penuh , dan Anda melakukannya untuk setiap item unik dalam daftar. Ini menjadikan ini solusi O (NK) (O (N ^ 2) dalam kasus terburuk). Menggunakan Counter()hanya membutuhkan O (N) waktu!
Martijn Pieters
185

Meminjam dari sini , ini dapat digunakan dengan Python 2.7:

from collections import Counter

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

Bekerja sekitar 4-6 kali lebih cepat daripada solusi Alex, dan 50 kali lebih cepat daripada one-liner yang diusulkan oleh newacct.

Untuk mengambil elemen yang muncul pertama dalam daftar jika terjadi ikatan:

def most_common(lst):
    data = Counter(lst)
    return max(lst, key=data.get)
Alex
sumber
3
Ini mungkin berguna bagi beberapa orang tetapi ... sayangnya Counter adalah subclass dict, dan OP mengatakan ia tidak dapat menggunakan kamus (karena item mungkin tidak dapat di hashable).
Danimal
13
Suka ini. Satu-liner oleh @newacct di atas mungkin sederhana, tetapi berjalan dalam O (n ^ 2); yaitu, di mana n adalah panjang daftar. Solusi ini adalah O (n).
BoltzmannBrain
5
Seperti kesederhanaan dan kecepatan ... mungkin tidak ideal untuk OP. Tapi sangat cocok untukku!
Thom
tidak mengembalikan item yang diindeks terendah. most_common mengembalikan daftar tidak terurut, dan meraih (1) hanya mengembalikan apa pun yang diinginkan.
AgentBawls
@ AgentBawls: most_commondisortir berdasarkan jumlah, bukan unordered. Yang mengatakan, itu tidak akan memilih elemen pertama dalam hal ikatan; Saya telah menambahkan cara lain untuk menggunakan penghitung yang memilih elemen pertama.
user2357112 mendukung Monica
58

Apa yang Anda inginkan dikenal dalam statistik sebagai mode, dan Python tentu saja memiliki fungsi bawaan untuk melakukan hal itu untuk Anda:

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

Perhatikan bahwa jika tidak ada "elemen paling umum" seperti kasus di mana dua teratas terikat , ini akan meningkat StatisticsError, karena secara statistik, tidak ada mode dalam kasus ini.

Luiz Berti
sumber
8
ini tidak memenuhi persyaratan OP tentang apa yang harus dikembalikan ketika ada lebih dari satu nilai paling umum - sebuah statistik. StatistikError dinaikkan
Keith Hall
5
Ups, tidak memenuhi persyaratan saat membacanya. Saya masih percaya jawaban ini memiliki nilai, karena tidak ada yang menyarankannya dalam pertanyaan ini, dan ini adalah solusi yang baik untuk masalah bagi orang-orang dengan persyaratan yang paling tidak membatasi. Ini adalah salah satu hasil teratas untuk "item paling umum di daftar python"
Luiz Berti
1
Dalam hal itu gunakan fungsi mode di panda DataFrames.
Elmex80s
1
Up-vote, yang ini harus lebih tinggi. Dan itu tidak sulit untuk memenuhi persyaratan OP dengan coba-kecuali sederhana (lihat stackoverflow.com/a/52952300/6646912 )
krassowski
1
@BreakBadSP jawaban Anda menggunakan lebih banyak memori karena tambahan set, dan masuk akal O(n^3).
Luiz Berti
9

Jika tidak hashable, Anda dapat mengurutkannya dan melakukan satu putaran atas hasil penghitungan item (item yang identik akan bersebelahan). Tetapi mungkin lebih cepat untuk membuatnya hashable dan menggunakan dict.

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
Lukáš Lalinský
sumber
Inilah cara yang lebih sederhana , ideone.com/Nq81vf , membandingkan dengan Counter()solusi Alex
Miguel
6

Ini adalah solusi 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

(terbalik digunakan untuk memastikan bahwa ia mengembalikan item indeks terendah)

ThisIsMeMoony
sumber
6

Tanpa persyaratan tentang indeks terendah, Anda dapat menggunakan collections.Counterini:

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'
Bapak baptis
sumber
Mudah dan cepat. Anda r Ayah baptis saya chain
chainstair
1
jawaban ini membutuhkan lebih banyak upvotes karena membahas tugas umum menghitung kejadian elemen dalam daftar menggunakan modul standar dan 2 baris kode
pcko1
5

Urutkan salinan daftar dan temukan jangka waktu terpanjang. Anda dapat menghiasi daftar sebelum mengurutkannya dengan indeks setiap elemen, dan kemudian memilih menjalankan yang dimulai dengan indeks terendah dalam kasus dasi.

Boojum
sumber
Item mungkin tidak dapat dibandingkan.
Pawel Furmaniak
4

Satu kalimat:

def most_common (lst):
    return max(((item, lst.count(item)) for item in set(lst)), key=lambda a: a[1])[0]
Willurd
sumber
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'
steveha
sumber
3

Solusi satu garis sederhana

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

Ini akan mengembalikan elemen yang paling sering dengan frekuensinya.

Shivam Agrawal
sumber
2

Anda mungkin tidak membutuhkan ini lagi, tetapi ini adalah apa yang saya lakukan untuk masalah yang sama. (Terlihat lebih panjang daripada karena komentar.)

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
Ed Holden
sumber
1
Anda bisa menggunakan counter [item] = counter.get (item, 0) +1 untuk mengganti bagian coba / kecuali
XueYu
1

Membangun jawaban Luiz , tetapi memuaskan kondisi " jika menarik item dengan indeks terendah harus dikembalikan " kondisi:

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

Contoh:

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

Sini:

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

Saya merasa tidak jelas ada metode di suatu tempat di perpustakaan standar yang akan memberi Anda hitungan setiap elemen, tetapi saya tidak dapat menemukannya.

Lennart Regebro
sumber
3
'max' adalah metode. Apakah Anda mengubah nama variabel?
Pratik Deoghare
1
Perhatikan bahwa set () juga memerlukan item hashable, untuk solusi tidak akan berfungsi dalam kasus ini.
Lukáš Lalinský
Tunggu, aku merindukan bagian yang tidak hashable. Tetapi jika benda-benda memiliki kesetaraan itu harus mudah membuatnya hashable.
Lennart Regebro
0

Ini adalah solusi lambat yang jelas (O (n ^ 2)) jika penyortiran atau hashing tidak layak, tetapi perbandingan kesetaraan ( ==) tersedia:

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]

Tetapi membuat barang-barang Anda mudah dipilah atau disortir (seperti yang direkomendasikan oleh jawaban lain) akan hampir selalu membuat menemukan elemen yang paling umum lebih cepat jika panjang daftar Anda (n) besar. O (n) rata-rata dengan hashing, dan O (n * log (n)) paling buruk untuk penyortiran.

Poin
sumber
Bagi para downvoter: apa yang salah dengan jawaban ini? Apakah ada jawaban lain yang memberikan solusi ketika penyortiran atau hashing tidak layak?
Poin
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'
Pratik Deoghare
sumber
Ini memiliki karakteristik kinerja yang buruk ketika n besar dan jumlah elemen unik juga besar: O (n) untuk konversi ke set dan O (m * n) = O (n ^ 2) untuk hitungan (di mana m adalah jumlah uniques). Sortir dan berjalan adalah O (n log n) untuk sortir dan 0 (n) untuk walk.
jmucchiello
1
Ya kamu benar. Sekarang saya tahu ini adalah solusi yang mengerikan dan mengapa. Terima kasih atas komentarnya !! :-)
Pratik Deoghare
0

Saya perlu melakukan ini dalam program terbaru. Saya akui, saya tidak bisa mengerti jawaban Alex, jadi inilah yang akhirnya saya dapatkan.

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

Saya menghitung waktu untuk solusi Alex dan sekitar 10-15% lebih cepat untuk daftar pendek, tetapi begitu Anda menggunakan lebih dari 100 elemen atau lebih (diuji hingga 200000) sekitar 20% lebih lambat.

pauleohare
sumber
-1

Hai ini adalah solusi yang sangat sederhana dengan O besar (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

Di mana nomor elemen dalam daftar yang mengulang sebagian besar waktu

Tempat kejadian
sumber
-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"

Israel Manzo
sumber
semua jawaban lainnya. Anda ingin saya menautkannya?
12 rhombi dalam kotak tanpa sudut
-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)
Ecanales
sumber
6
Harap berikan beberapa informasi tentang kode Anda, hanya memposting kode yang bukan jawaban lengkap
jhhoff02
1
Apakah ada alasan seseorang harus menggunakan ini dari 15 jawaban lainnya?
Semua Pekerja Penting
-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)
Pronoy
sumber