【Python 雑談・雑学 + coding challenge】collections モジュールの Counter クラスと most_common メソッドを利用してシーケンス内の最頻出要素を取得しよう! 投稿一覧へ戻る

Tags: Python , miscellaneous , namedtuple , collections , challenge , most_common , sorted , counter

Published 2020年8月5日23:30 by T.Tsuyoshi

今回もちょっとしたコーディング問題から。


次のような文字列のリストがあります。


WORDS = ['this', 'is', 'ordereddict', 'an', 'elementary', 'test', 'example', 'hippopotamus']



これらの文字列の中から、同じ文字 ( character / letter ) が最も多く使われている単語を選び出して返す関数を作成します。


出力結果は以下のようになります。


ordereddict: 'd' 3 times
elementary: 'e' 3 times
hippopotamus: 'p' 3 times



何故かというと...


'this': 同じ文字は使われていません。

'is': 同じ文字は使われていません。

'ordereddict': 'd' が 3 回使われています。

'an': 同じ文字は使われていません。

'elementary': 'e' が 3 回使われています。

'test': 't' が 2 回使われています。

'example': 'e' が 2 回使われています。

'hippopotamus': 'p' が 3 回使われています。



ですから、同じ文字が 3 回使われている 3 つの単語を、最も多く登場する文字とその回数の情報も含めて返しています。


今回の問題を解決する際に役に立つのが、collections モジュールの Counter クラスと、そのクラスの most_common() メソッドです。


Counter クラスは dict のサブクラスで、与えられたシーケンスの要素をキー、その要素の出現数を値とするコレクション ( Counter オブジェクト ) です。


from collections import Counter

c = Counter('reindeer')

print(c)
# Counter({'e': 3, 'r': 2, 'i': 1, 'n': 1, 'd': 1})



そして、Counter オブジェクトの most_common() メソッドを利用すると、出現頻度の高い ( 辞書要素の中で値が大きい ) 順に並べられたタプルのリストを取得することが出来ます。


print(c.most_common())
# [('e', 3), ('r', 2), ('i', 1), ('n', 1), ('d', 1)]



最も値の大きいもの一つだけを取得したい場合は、most_common() メソッドに 1 を渡します。


print(c.most_common(1))
# [('e', 3)]



では実装チャレンジです!


制限時間: 50 分

from collections import Counter

WORDS = ['this', 'is', 'ordereddict', 'an', 'elementary', 'test', 'example', 'hippopotamus']


def most_repeated_word(words):
pass


出力結果:
ordereddict: 'd' 3 times
elementary: 'e' 3 times
hippopotamus: 'p' 3 times



いかがでしたか?


Counter.most_common() の返却値はタプルのリストで、タプルのインデックス 0 に文字、インデックス 1 に出現回数を表す値が入っていますから、それを取り出そうとするとインデックス表記で目がチカチカするコードになってしまいます。


そこで、同じ collections モジュールの namedtuple も活用してみました。


これにより、タプルの各要素にインデックス番号だけではなく、属性名でアクセス可能になります。


また、1 つの文字列 (単語) を解析するためのヘルパー関数 count_letters() を作成しました。


実装例は以下のようです。参考にしてください。


from collections import Counter, namedtuple


ParseWord = namedtuple('ParseWord', ('word', 'letter', 'count'))


def count_letters(word):
"""
文字列を解析し、その中に最も多く含まれる文字とそのカウント数からなる ParseWord 名前付きタプルを返すヘルパー関数。

:param word: シーケンス (この例題では文字列)
:return: 渡されたシーケンス、最頻出要素、回数 から成る ParseWord 名前付きタプル。例: ('test', 't', 2)
"""

c = Counter(word).most_common(1)[0]
return ParseWord(word=word, letter=c[0], count=c[1])



上の count_letters() に 'test' が渡されてきた場合、

1: Counter(word) で、Counter({'t': 2, 'e': 1, 's': 1}) オブジェクトが作成され、
2: Counter(word).most_common(1) で、[('t', 2)] が返されてくるので、
3: Counter(word).most_common(1)[0] で、('t', 2) が取得できます。

3: が 'test' を解析した結果最も多く含まれている文字とその回数を表すタプルです。


def most_repeated_word(words):
"""
リストを構成する各文字列を解析し、同一文字を最も多く含む文字列の解析結果を ParseWord 名前付きタプルで返す。

:param words: 解析する文字列からなるリスト
:return: ParseWord 名前付きタプルからなるリスト
"""

result = []
for word in words:
result.append(count_letters(word))

# 各文字列の解析結果を ParseWord 名前付きタプルとして含むリストを、カウント数の多い順に並べ替えます。
sorted_words = sorted(result, key=lambda x: x.count, reverse=True)

# 最高出現回数が同じ文字列が複数ある場合を考慮した処理です(今回の例では 3 つ)。
# sorted_words リストはカウント数の高い順に並べ替えているので、インデックス 0 のカウント数は最大値であることが保証されています。
# list comprehension を利用して、カウント数が最大値と等しい要素だけでリストを作成し返します。

max_letter_count = sorted_words[0].count
return [word for word in sorted_words if word.count == max_letter_count]


WORDS = ['this', 'is', 'ordereddict', 'an', 'elementary', 'test', 'example', 'hippopotamus']
res = most_repeated_word(WORDS)
for i in res:
# 名前付きタプルを利用しているため、インデックス番号ではなく有意義な属性名で参照できます。
print(f"{i.word}: '{i.letter}' {i.count} times")

# ordereddict: 'd' 3 times
# elementary: 'e' 3 times
# hippopotamus: 'p' 3 times

この投稿をメールでシェアする

0 comments

コメントはまだありません。

コメントを追加する(不適切と思われるコメントは削除する場合があります)