Pythonリストで最も一般的な要素を見つける効率的な方法は何ですか?
リストのアイテムがハッシュ可能でない可能性があるため、辞書を使用できません。また、描画の場合、最も低いインデックスのアイテムが返されます。例:
>>> most_common(['duck', 'duck', 'goose'])
'duck'
>>> most_common(['goose', 'duck', 'duck', 'goose'])
'goose'
Pythonリストで最も一般的な要素を見つける効率的な方法は何ですか?
リストのアイテムがハッシュ可能でない可能性があるため、辞書を使用できません。また、描画の場合、最も低いインデックスのアイテムが返されます。例:
>>> most_common(['duck', 'duck', 'goose'])
'duck'
>>> most_common(['goose', 'duck', 'duck', 'goose'])
'goose'
回答:
非常に多くの解決策が提案されているので、明らかな解決策(ハッシュ可能ではないが比較可能な要素について)と考えるものは誰も提案していないことに驚いています-[ 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]
groupby
最初にソートが必要です(O(NlogN)); Counter()
with を使用するとmost_common()
、heapqを使用して最も頻度の高いアイテムを検索するため、これに勝ることができます(1つのアイテムの場合、O(N)時間です)。Counter()
今重く(カウントはCループで行われます)に最適化され、それが簡単に小さなリストに対してこのソリューションを打つことができます。大きなリストの場合は、それを水から吹き飛ばします。
よりシンプルなワンライナー:
def most_common(lst):
return max(set(lst), key=lst.count)
set(lst)
、リスト全体を再度確認する必要があります)…とはいえ、ほとんどの用途ではおそらく十分高速です...
set(lst)
てlst
、それがあまりにも非ハッシュ可能要素で動作します。遅いですが。
list.count()
はリスト全体を全探索する必要があり、リスト内のすべての一意のアイテムに対してそのようにします。これにより、これはO(NK)ソリューションになります(最悪の場合はO(N ^ 2))。を使用すると、Counter()
O(N)時間しかかかりません!
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)
most_common
順不同ではなく、カウントでソートされます。とはいえ、同点の場合、最初の要素は選択されません。最初の要素を選択するカウンターを使用する別の方法を追加しました。
必要なのは統計ではモードと呼ばれ、Pythonにはもちろん、正確にそれを行うための組み込み関数があります。
>>> from statistics import mode
>>> mode([1, 2, 2, 3, 3, 3, 3, 3, 4, 5, 6, 6, 6])
3
StatisticsError
統計的に言えば、この場合にはモードがないため、上位2つが同点である場合などの「最も一般的な要素」がない場合は、これが発生します。
set
、もっともらしいO(n^3)
です。
それらがハッシュ可能でない場合は、それらをソートして、アイテムをカウントする結果に対して単一のループを実行できます(同一のアイテムは互いに隣接します)。しかし、それらをハッシュ可能にして、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
これは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は、最も低いインデックスアイテムを確実に返すために使用されます)
リストのコピーを並べ替え、最長の実行を見つけます。各要素のインデックスでソートする前にリストを装飾し、同順位の場合は最も低いインデックスから始まる実行を選択できます。
最小のインデックスに関する要件がなければ、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'
# 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'
シンプルな1行ソリューション
moc= max([(lst.count(chr),chr) for chr in set(lst)])
それはその頻度で最も頻繁な要素を返します。
あなたはおそらくこれはもう必要ないでしょうが、これは私が同じような問題のためにやったことです。(コメントがあるので、見た目より長く見えます。)
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
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
ここに:
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
標準ライブラリのどこかに、各要素のカウントを提供する方法があるかどうか漠然と感じていますが、見つかりません。
これは、ソートもハッシュ化も実行できないが、等値比較(==
)が利用可能な場合、明らかに遅いソリューション(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))。
>>> 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'
最近のプログラムでこれを行う必要がありました。認めますが、アレックスの答えが理解できなかったので、結局これで終わりです。
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%遅くなりました。
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"
def most_common(lst):
if max([lst.count(i)for i in lst]) == 1:
return False
else:
return max(set(lst), key=lst.count)