検索ガイド -Search Guide-

単語と単語を空白で区切ることで AND 検索になります。
例: python デコレータ ('python' と 'デコレータ' 両方を含む記事を検索します)
単語の前に '-' を付けることで NOT 検索になります。
例: python -デコレータ ('python' は含むが 'デコレータ' は含まない記事を検索します)
" (ダブルクオート) で語句を囲むことで 完全一致検索になります。
例: "python data" 実装 ('python data' と '実装' 両方を含む記事を検索します。'python data 実装' の検索とは異なります。)
  • ただいまサイドメニューのテスト中/ただいまサイドメニューのテスト中
  • ただいまサイドメニューのテスト中/ただいまサイドメニューのテスト中
  • ただいまサイドメニューのテスト中/ただいまサイドメニューのテスト中
  • ただいまサイドメニューのテスト中/ただいまサイドメニューのテスト中
  • ただいまサイドメニューのテスト中/ただいまサイドメニューのテスト中
  • ただいまサイドメニューのテスト中/ただいまサイドメニューのテスト中
  • ただいまサイドメニューのテスト中/ただいまサイドメニューのテスト中
  • ただいまサイドメニューのテスト中/ただいまサイドメニューのテスト中
  • ただいまサイドメニューのテスト中/ただいまサイドメニューのテスト中
  • ただいまサイドメニューのテスト中/ただいまサイドメニューのテスト中
  • ただいまサイドメニューのテスト中/ただいまサイドメニューのテスト中
>>
python_coding_challenge

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

Published 2020年8月5日23:30 by mootaro23

SUPPORT UKRAINE

- Your indifference to the act of cruelty can thrive rogue nations like Russia -

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


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


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
この記事に興味のある方は次の記事にも関心を持っているようです...
- People who read this article may also be interested in following articles ... -
【Python 雑談・雑学 + coding challenge】シーケンス ( sequence ) における インデックス ( index ) を使った要素 1 つの取り出しと、スライス ( slice ) を利用した場合の取り出しの違いをちゃんと理解していますか?
【 Effective Python, 2nd Edition 】独自のコンテナタイプ ( custom container types ) を定義するなら collections.abc クラスから派生させると手間無しです!
【Python 雑談・雑学 + coding challenge】itertools モジュールの combinations() メソッドを自分で実装してみよう!
【Python 雑談・雑学 + coding challenge】sorted 組み込み関数の key パラメータをうまく使って、カスタムオブジェクトを簡単にソートしよう! __getitem__、__len__ 特殊関数 ( special methods, dunder methods ) を実装すれば立派なシーケンス ( sequence ) です
【Python 雑談・雑学 + coding challenge】Unicode の正規化処理 ( normalization ) を利用して、diacritical marks ( 発音区別符号 ) を取り除こう! テキスト解析の前処理としても重要です!
【Python 雑談・雑学 + coding challenge】Python data structure の1つ、set を活用していますか? 複数のシーケンスの包含関係を調べるには最適です
【 Effective Python, 2nd Edition 】__set_name__ デスクリプタ専用特殊関数 ( special method for descriptor ) を利用してデスクリプタインスタンスを割り当てたクラス変数名を取得し、コードの冗長性を排除しよう!