リストで最も一般的な要素を見つける


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]

もちろんこれはもっと簡潔に書くことができますが、私は最大限の明快さを目指しています。2つの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計算中にグループ化ごとに1回呼び出される補助関数は、グループを受け取り、内部でグループ化を解除します- (item, iterable)イテラブルのアイテムも2アイテムのタプル(item, original index)[[のアイテムSL]]]である2つのアイテムを持つタプル。

その後、補助機能は、グループの反復可能な、内のエントリの数の両方を決定するためにループを使用し、最小元のインデックスと、これらは、最小のインデックス記号が変更された「品質キー」の組み合わせとしてそれらを返します。そのため、max操作は、元のリストで以前に発生した項目を「より良い」と見なします。

このコードは、時間と空間のビッグOの問題について少し心配していれば、はるかに単純になる可能性があります。

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の2乗)時間(L.indexすべてのアイテムのを取得するため) 。時期尚早な最適化はプログラミングにおけるすべての悪の根本ですが、O(N log N)が利用可能な場合に意図的にO(N二乗)アプローチを選択すると、スケーラビリティの粒度に過度に影響します!-)

最後に、明確さとパフォーマンスより「oneliners」を好む人のために、適切にマングルされた名前のボーナス1-linerバージョン:-)。

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 2016

2
groupby最初にソートが必要です(O(NlogN)); Counter()with を使用するとmost_common()、heapqを使用して最も頻度の高いアイテムを検索するため、これに勝ることができます(1つのアイテムの場合、O(N)時間です)。Counter()今重く(カウントはCループで行われます)に最適化され、それが簡単に小さなリストに対してこのソリューションを打つことができます。大きなリストの場合は、それを水から吹き飛ばします。
Martijn Pieters

ネクタイの「最低インデックス」要件のみが、これをこの問題に対する有効な解決策にします。より一般的なケースでは、間違いなくCounterアプローチを使用する必要があります。
Martijn Pieters

@MartijnPietersおそらく、アイテムがハッシュ化できない可能性があるという質問の一部を見逃した可能性があります。
2017年

@wim右、およびアイテムがハッシュ可能でない場合。これにより、セットと最大の投票数はますます矛盾します。
Martijn Pieters

442

よりシンプルなワンライナー:

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

24
OPは、[..]を描画する場合、最も低いインデックスのアイテムを返す必要があると述べています。このコードは、一般的に、その要件を満たしていません。
Stephan202、2009年

2
さらに、OPは要素はハッシュ可能でなければならないことを述べました:セットはハッシュ可能なオブジェクトを含まなければなりません。
エリックOレビゴット2009年

2
さらに、このアプローチはアルゴリズム的に低速です(の各要素についてset(lst)、リスト全体を再度確認する必要があります)…とはいえ、ほとんどの用途ではおそらく十分高速です...
Eric O Lebigot

9
あなたは置き換えることができset(lst)lst、それがあまりにも非ハッシュ可能要素で動作します。遅いですが。
newacct 2009年

24
これは魅力的に見えるかもしれませが、アルゴリズムの観点からは、これはひどいアドバイスです。list.count()はリスト全体を全探索する必要があり、リスト内のすべての一意のアイテムに対してそのようにします。これにより、これはO(NK)ソリューションになります(最悪の場合はO(N ^ 2))。を使用すると、Counter()O(N)時間しかかかりません!
Martijn Pieters

185

ここから借り、これはPython 2.7で使用できます:

from collections import Counter

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

Alexのソリューションよりも4〜6倍速く動作し、newacctが提案するワンライナーよりも50倍高速です。

同順位の場合にリストの最初に出現する要素を取得するには:

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

3
これは一部の人にとって便利かもしれませんが...残念ながらCounterはdictサブクラスであり、OPは辞書を使用できないと述べました(アイテムがハッシュ可能でない可能性があるため)。
Danimal 2014

13
これが大好き。上記の@newacctによるワンライナーは単純かもしれませんが、O(n ^ 2)で実行されます。つまり、nはリストの長さです。この解はO(n)です。
BoltzmannBrain

5
シンプルさとスピードのように...多分OPには理想的ではありません。しかし、私にぴったりです!
Thom

最も低いインデックスのアイテムを返しません。most_commonは順序付けされていないリストを返し、(1)を取得すると、好きなだけ戻ります。
AgentBawls 2017

@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統計的に言えば、この場合にはモードがないため、上位2つが同点ある場合などの「最も一般的な要素」がない場合は、これが発生します。


8
これは、最も一般的な値が複数ある場合に何を返すかというOPの要件を満たしていません-統計
キースホール

5
おっと、それを読むときに要件を逃しました。この質問で誰もそれを示唆していないので、私はまだこの回答が価値があると信じています、そしてそれは最も制限の少ない要件を持つ人々のための問題の良い解決策です。これは、「リストpythonの最も一般的なアイテム」の上位の結果の1つです
Luiz Berti

1
その場合は、pandas DataFrameでモード関数を使用します。
Elmex80s 2017年

1
賛成票を投じてください、これはもっと高いはずです。そして、単純なtry-exceptでOPの要件を満たすのはそれほど難しくありません(私のstackoverflow.com/a/52952300/6646912を参照)
krassowski

1
@BreakBadSPあなたの答えは追加のためにメモリをより多く使用しset、もっともらしいO(n^3)です。
Luiz Berti 2018

9

それらがハッシュ可能でない場合は、それらをソートして、アイテムをカウントする結果に対して単一のループを実行できます(同一のアイテムは互いに隣接します)。しかし、それらをハッシュ可能にして、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

Alexのソリューションと比較したideone.com/Nq81vfの簡単な方法を次に示しCounter()ます
Miguel

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

(reversedは、最も低いインデックスアイテムを確実に返すために使用されます)


5

リストのコピーを並べ替え、最長の実行を見つけます。各要素のインデックスでソートする前にリストを装飾し、同順位の場合は最も低いインデックスから始まる実行を選択できます。


アイテムは比較できない場合があります。
Pawel Furmaniak 2013年

5

最小のインデックスに関する要件がなければ、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'

簡単かつ高速。あなたは私のゴッドファーザー😏✌rを
chainstairを

この回答は、標準モジュールと2行のコードを使用してリスト内の要素の出現回数をカウントするという一般的なタスクに対処するため、より多くの投票が必要です
pcko1

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

シンプルな1行ソリューション

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を使用して、try / exceptパーツを置き換えることができます
XueYu

1

Luizの回答に基づいて構築されていますが、「描画の場合、最も低いインデックスのアイテムが返される必要があります」という条件を満たします。

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
「max」はメソッドです。変数の名前を変更しますか?
Pratik Deoghare、2009年

1
set()もハッシュ可能なアイテムを必要とすることに注意してください。この場合、ソリューションは機能しません。
ルーカス・ラリンズキー

待って、私はハッシュ可能ではないというその部分を逃しました。しかし、オブジェクトに同等性がある場合、それらをハッシュ可能にするのは簡単です。
Lennart Regebro、2009年

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))。


反対投票者へ:この回答の何が問題になっていますか?並べ替えもハッシュも実行できない場合、他の回答は解決策を提供しますか?
pts 2018年

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)
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.