Open1

[Python] Counterチートシート()

TakaTaka

目的

「PythonのCounter」の学習
親記事
https://zenn.dev/taka_noiri/scraps/3ff6117cccf4e2

対象読者

三ヶ月後くらいの自分

学び

  • Counterの使用方法
  • Counterをいつ使用するか

Counterチートシート

Pythonのcollectionsモジュールに含まれるCounterクラスは、データの頻度を効率的にカウントし、分析するタスクを簡単かつ効率的に実行するための強力なツールです。


目次

  1. Counterとは?
  2. 基本的な使い方
  3. 主なメソッドと機能
  4. アルゴリズム問題での活用例
  5. Counterの利点と注意点
  6. Counterと他のデータ構造との比較
  7. よくある質問

Counterとは?

Counterは、Pythonのcollectionsモジュールに含まれるクラスで、イテラブルなデータ(リスト、文字列、タプルなど)の各要素の出現回数をカウントするためのツールです。内部的には辞書(dict)を拡張しており、キーとして要素、バリューとしてその出現回数を保持します。


基本的な使い方

インポート方法

from collections import Counter

要素のカウント

from collections import Counter

# リスト内の要素をカウント
fruits = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple']
fruit_counts = Counter(fruits)
print(fruit_counts)
# 出力: Counter({'apple': 3, 'banana': 2, 'orange': 1})

# 文字列内の文字をカウント
text = "hello world"
char_counts = Counter(text)
print(char_counts)
# 出力: Counter({'l': 3, 'o': 2, 'h': 1, 'e': 1, ' ': 1, 'w': 1, 'r': 1, 'd': 1})

主なメソッドと機能

1. most_common([n])

最も出現回数の多い要素を取得します。nを指定すると上位n個を返します。

from collections import Counter

words = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple']
word_counts = Counter(words)
top_two = word_counts.most_common(2)
print(top_two)
# 出力: [('apple', 3), ('banana', 2)]

2. elements()

要素を出現回数分だけ繰り返して返します。カウントが0以下の要素は無視されます。

from collections import Counter

c = Counter({'a': 2, 'b': 1, 'c': 0})
elements = list(c.elements())
print(elements)
# 出力: ['a', 'a', 'b']

3. update(iterable_or_mapping)

カウンターを更新します。イテラブルなオブジェクトやマッピングを渡すことで、要素のカウントを増加させます。

from collections import Counter

c = Counter({'a': 2, 'b': 1})
c.update({'a': 1, 'c': 3})
print(c)
# 出力: Counter({'c': 3, 'a': 3, 'b': 1})

4. subtract(iterable_or_mapping)

指定した要素のカウントを減少させます。

from collections import Counter

c = Counter({'a': 3, 'b': 1, 'c': 3})
c.subtract({'a': 1, 'c': 2})
print(c)
# 出力: Counter({'a': 2, 'c': 1, 'b': 1})

5. elements()

カウンター内の要素を反復可能な形式で返します。カウントが0以下の要素は無視されます。

from collections import Counter

c = Counter({'a': 2, 'b': 1, 'c': 0})
elements = list(c.elements())
print(elements)
# 出力: ['a', 'a', 'b']

6. clear()

カウンターを空にします。

from collections import Counter

c = Counter({'a': 2, 'b': 1})
c.clear()
print(c)
# 出力: Counter()

7. &= 演算子によるカウンターの共通部分

&= 演算子を使用すると、2つの Counter オブジェクトの共通部分を取得できます。これは各要素について最小のカウントを保持します。

from collections import Counter

c1 = Counter({'a': 3, 'b': 1, 'c': 2})
c2 = Counter({'a': 1, 'b': 2, 'c': 2})

c1 &= c2
print(c1)
# 出力: Counter({'a': 1, 'c': 2, 'b': 1})

説明:

  • c1 &= c2c1c2 の共通部分を計算し、c1 を更新します。
  • 各要素について、c1c2 のカウントのうち小さい方の値が c1 に設定されます。
  • この操作により、両方の Counter に存在する要素のみが残り、そのカウントは両方のカウンターで最小の値になります。

用途例:

  • 複数のデータセット間で共通の要素とその出現回数を知りたい場合。
  • アルゴリズム問題で、複数の条件を同時に満たす要素を効率的に抽出したい場合。

アルゴリズム問題での活用例

1. アナグラム判定

2つの文字列がアナグラム(同じ文字を同じ回数含む)であるかを判定します。

from collections import Counter

def isAnagram(s: str, t: str) -> bool:
    return Counter(s) == Counter(t)

# 使用例
print(isAnagram("listen", "silent"))  # 出力: True
print(isAnagram("hello", "billion"))  # 出力: False

2. 重複要素の検出

リスト内に重複する要素が存在するかを判定します。

from collections import Counter

def containsDuplicate(nums):
    num_counts = Counter(nums)
    for count in num_counts.values():
        if count > 1:
            return True
    return False

# 使用例
print(containsDuplicate([1, 2, 3, 1]))  # 出力: True
print(containsDuplicate([1, 2, 3, 4]))  # 出力: False

3. 単語の出現回数カウント

文章内の単語の出現回数をカウントし、頻出単語を取得します。

from collections import Counter

def topKFrequent(words, k):
    counts = Counter(words)
    return [word for word, _ in counts.most_common(k)]

# 使用例
print(topKFrequent(["i", "love", "leetcode", "i", "love", "coding"], 2))
# 出力: ['i', 'love']

4. 文字列の最小ウィンドウ問題(まだわかっていない)

指定した条件を満たす最小のウィンドウを文字列から見つける問題にCounterが役立ちます。

from collections import Counter

def minWindow(s: str, t: str) -> str:
    if not t or not s:
        return ""
    
    dict_t = Counter(t)
    required = len(dict_t)
    
    l, r = 0, 0
    formed = 0
    window_counts = {}
    ans = float("inf"), None, None
    
    while r < len(s):
        character = s[r]
        window_counts[character] = window_counts.get(character, 0) + 1
        
        if character in dict_t and window_counts[character] == dict_t[character]:
            formed += 1
        
        while l <= r and formed == required:
            character = s[l]
            
            if r - l + 1 < ans[0]:
                ans = (r - l + 1, l, r)
            
            window_counts[character] -= 1
            if character in dict_t and window_counts[character] < dict_t[character]:
                formed -= 1
            
            l += 1
        
        r += 1
    
    return "" if ans[0] == float("inf") else s[ans[1]: ans[2] + 1]

# 使用例
print(minWindow("ADOBECODEBANC", "ABC"))  # 出力: "BANC"

5. 負数も含む要素のカウント

Counterは負の数やゼロもカウント可能です。

from collections import Counter

def findPairs(nums, k):
    if k < 0:
        return 0
    counts = Counter(nums)
    result = 0
    for num in counts:
        if k > 0 and (num + k) in counts:
            result += 1
        elif k == 0 and counts[num] > 1:
            result += 1
    return result

# 使用例
print(findPairs([3, 1, 4, 1, 5], 2))  # 出力: 2
print(findPairs([1, 2, 3, 4, 5], 1))  # 出力: 4

Counterの利点と注意点

利点

  1. 簡潔なコード: 少ないコードで要素のカウントが可能。
  2. 高速な処理: 内部的にハッシュテーブルを使用しており、カウント操作が高速。
  3. 豊富なメソッド: most_common(), elements(), update(), subtract()など、頻度分析に役立つメソッドが豊富。
  4. 柔軟性: リスト、文字列、タプルなど、さまざまなイテラブルなデータを扱える。

注意点

  1. ハッシュ可能なオブジェクトのみ: Counterはハッシュ可能なオブジェクト(例:数値、文字列、タプルなど)のみをカウントできます。リストや辞書などのミュータブルなオブジェクトは使用できません。

    from collections import Counter
    
    # 有効
    c = Counter([(1, 2), (3, 4), (1, 2)])
    print(c)
    # 出力: Counter({(1, 2): 2, (3, 4): 1})
    
    # 無効(エラー)
    # c = Counter([[1, 2], [3, 4], [1, 2]])  # TypeError: unhashable type: 'list'
    
  2. 大規模データの扱い: 非常に大きなデータセットを扱う場合、メモリ消費に注意が必要です。Counterは内部的に辞書を使用しているため、大量のユニークな要素が存在するとメモリ使用量が増加します。

  3. 順序の保証: Python 3.7以降、辞書は挿入順を保持しますが、Counter自体は順序を保証しません。要素を順序付けて処理する必要がある場合は、most_common()などのメソッドを使用してください。


Counterと他のデータ構造との比較

Counter vs dict

  • Counterの利点:

    • 存在しないキーにアクセスすると自動的に0を返します。
    • 頻度分析に特化したメソッド(most_common()など)が利用可能。
    from collections import Counter
    
    c = Counter({'a': 2, 'b': 1})
    print(c['c'])  # 出力: 0
    
    d = {'a': 2, 'b': 1}
    print(d.get('c', 0))  # 出力: 0
    # print(d['c'])  # KeyError
    
  • dictの利点:

    • 任意の値を保持でき、Counterに特化した機能が不要な場合に柔軟。
    • Counterdictを継承しているため、dictの機能も利用可能。

Counter vs defaultdict

  • Counterの利点:

    • 要素のカウントに特化し、カウント関連のメソッドが豊富。
    • デフォルト値が自動的に0に設定される点はdefaultdictと共通。
  • defaultdictの利点:

    • 任意のデフォルト値を設定可能。
    • Counterdefaultdict(int)としても機能するが、カウント以外の用途には向かない。
    from collections import defaultdict, Counter
    
    # defaultdictの例
    dd = defaultdict(int)
    dd['a'] += 1
    print(dd)  # 出力: defaultdict(<class 'int'>, {'a': 1})
    
    # Counterの例
    c = Counter()
    c['a'] += 1
    print(c)   # 出力: Counter({'a': 1})
    

よくある質問

Q1. Counterの比較はどのように行われますか?

Counterオブジェクト同士を比較すると、各要素のカウントが一致するかどうかが評価されます。

from collections import Counter

c1 = Counter({'a': 2, 'b': 1})
c2 = Counter({'b': 1, 'a': 2})
c3 = Counter({'a': 1, 'b': 2})

print(c1 == c2)  # 出力: True
print(c1 == c3)  # 出力: False

Q2. Counterはイテラブル以外のデータ型を扱えますか?

Counterはイテラブルなデータ型(リスト、文字列、タプルなど)を扱うことが前提です。イテラブルでないデータ型(例えば、整数)は直接カウントできません。

Q3. Counterで要素を削除する方法は?

Counterから特定の要素を削除するには、delキーワードを使用します。また、カウントが0以下になる要素は自動的に削除されます。

from collections import Counter

c = Counter({'a': 2, 'b': 1, 'c': 3})
del c['b']
print(c)
# 出力: Counter({'c': 3, 'a': 2})

c.subtract({'a': 2, 'c': 1})
print(c)
# 出力: Counter({'c': 2})

Q4. Counterはソートできますか?

Counter自体はソートをサポートしていませんが、most_common()メソッドを使用して出現頻度の高い順に要素を取得できます。逆順や特定の基準でソートしたい場合は、sorted()関数と組み合わせて使用します。

from collections import Counter

c = Counter({'a': 3, 'b': 1, 'c': 2})

# 出現頻度の高い順
print(c.most_common())
# 出力: [('a', 3), ('c', 2), ('b', 1)]

# アルファベット順にソート
sorted_items = sorted(c.items())
print(sorted_items)
# 出力: [('a', 3), ('b', 1), ('c', 2)]

Q5. Counterのカウントをリセットする方法は?

Counterのカウントをリセットするには、clear()メソッドを使用します。

from collections import Counter

c = Counter({'a': 2, 'b': 1})
c.clear()
print(c)
# 出力: Counter()

まとめ

PythonのCounterクラスは、データの頻度を効率的にカウント・分析するための強力なツールです。アルゴリズム問題では、文字列操作、配列の頻度分析、アナグラム判定など、さまざまな場面で活用できます。以下のポイントを押さえておくと、面接での活用がスムーズになります。

  • 基本操作をマスター: 要素のカウント、most_common()elements()などの基本メソッドを理解する。
  • 応用問題に挑戦: アナグラム判定や重複要素の検出など、実際の問題でCounterを使ってみる。
  • 効率性を理解: Counterの時間・空間計算量を理解し、他のデータ構造との比較を行う。
  • コードの可読性: Counterを使うことで、コードがより簡潔で読みやすくなることを活用する。