Aho-Corasickアルゴリズムを使用した高速でメモリ効率の良い複数パターン文字列検索ライブラリ「pyahocorasick」を動かして速度比較を行う

初めに

値の関連付けが必要で高速に文字列を検索したい時に使えるらしいpyahocorasickを触ってみます

github.com

開発環境

項目 バージョン
OS macOS 26.0.1
Python 3.12.12
uv 0.9.5
pyahocorasick 2.3.0

環境構築

まずはプロジェクトの初期化を行います

# プロジェクトディレクトリで実行
uv init --no-readme

# Pythonバージョンを固定
uv python pin 3.12

開発用依存関係をインストールします

uv add --dev pytest twine setuptools wheel

本ライブラリをUnicode版でビルドします

AHOCORASICK_UNICODE=yes uv pip install -e .

実行

実際に以下のようなでもスクリプトを作成して実行してみます

import ahocorasick

# 47都道府県
PREFECTURES = [
    "北海道", "青森県", "岩手県", "宮城県", "秋田県", "山形県", "福島県",
    "茨城県", "栃木県", "群馬県", "埼玉県", "千葉県", "東京都", "神奈川県",
    "新潟県", "富山県", "石川県", "福井県", "山梨県", "長野県", "岐阜県",
    "静岡県", "愛知県", "三重県", "滋賀県", "京都府", "大阪府", "兵庫県",
    "奈良県", "和歌山県", "鳥取県", "島根県", "岡山県", "広島県", "山口県",
    "徳島県", "香川県", "愛媛県", "高知県", "福岡県", "佐賀県", "長崎県",
    "熊本県", "大分県", "宮崎県", "鹿児島県", "沖縄県",
]


def main():
    print("=" * 60)
    print("pyahocorasick 基本デモ")
    print("=" * 60)

    # 1. Automatonを作成
    print("\n【1. Automatonの作成】")
    automaton = ahocorasick.Automaton()
    print(f"  Automaton created: {automaton}")

    # 2. キーワードを登録(値も関連付け可能)
    print("\n【2. キーワードの登録】")
    for i, pref in enumerate(PREFECTURES):
        # キーワードに辞書を関連付ける例
        automaton.add_word(pref, {"name": pref, "index": i})
    print(f"  登録キーワード数: {len(automaton)}")

    # 3. オートマトンを構築
    print("\n【3. オートマトンの構築】")
    automaton.make_automaton()
    print(f"  状態: {automaton}")

    # 4. テキストを検索
    print("\n【4. テキスト検索】")
    text = "東京都から京都府へ行き、大阪府と兵庫県を訪問した後、北海道と沖縄県にも足を延ばした"
    print(f"  検索テキスト: 「{text}」")
    print(f"  テキスト長: {len(text)}文字")

    print("\n【5. 検索結果】")
    matches = []
    for end_index, value in automaton.iter(text):
        start_index = end_index - len(value["name"]) + 1
        matches.append({
            "start": start_index,
            "end": end_index,
            "keyword": value["name"],
            "index": value["index"],
        })

    for match in matches:
        print(f"  位置 {match['start']:2d}-{match['end']:2d}: {match['keyword']} (index={match['index']})")

    print(f"\n  マッチ数: {len(matches)}件")

    # 6. 追加機能のデモ
    print("\n" + "=" * 60)
    print("追加機能")
    print("=" * 60)

    # キーワードの存在確認
    print("\n【キーワードの存在確認】")
    print(f"  '東京都' in automaton: {'東京都' in automaton}")
    print(f"  '東京' in automaton: {'東京' in automaton}")

    # 値の取得
    print("\n【値の取得】")
    print(f"  automaton.get('大阪府'): {automaton.get('大阪府')}")
    print(f"  automaton.get('存在しない', 'default'): {automaton.get('存在しない', 'default')}")

    # 統計情報
    print("\n【統計情報】")
    print(f"  キーワード数: {len(automaton)}")
    print(f"  Unicode mode: {ahocorasick.unicode}")


if __name__ == "__main__":
    main()

結果は以下のとおりです

============================================================
pyahocorasick 基本デモ
============================================================1. Automatonの作成】
  Automaton created: <ahocorasick.Automaton object at 0x10095d8b0>2. キーワードの登録】
  登録キーワード数: 473. オートマトンの構築】
  状態: <ahocorasick.Automaton object at 0x10095d8b0>4. テキスト検索】
  検索テキスト: 「東京都から京都府へ行き、大阪府と兵庫県を訪問した後、北海道と沖縄県にも足を延ばした」
  テキスト長: 41文字

【5. 検索結果】
  位置  0- 2: 東京都 (index=12)
  位置  5- 7: 京都府 (index=25)
  位置 12-14: 大阪府 (index=26)
  位置 16-18: 兵庫県 (index=27)
  位置 26-28: 北海道 (index=0)
  位置 30-32: 沖縄県 (index=46)

  マッチ数: 6============================================================
追加機能
============================================================

【キーワードの存在確認】
  '東京都' in automaton: True
  '東京' in automaton: False

【値の取得】
  automaton.get('大阪府'): {'name': '大阪府', 'index': 26}
  automaton.get('存在しない', 'default'): default

【統計情報】
  キーワード数: 47
  Unicode mode: 1

他の文字検索ライブラリとの比較

調査をすると以下のようなものが見つかりました

ライブラリ 実装言語 値関連付け 部分一致 Unicode Pickle メンテナンス
pyahocorasick C 活発
ahocorasick_rs Rust 活発
flashtext Python 低調
ahocorapy Python 低調
regex C - - 活発

また機能や性能を調べると以下のようになります

相対速度(pyahocorasick = 1.0として)

以下はWeb上の各種ベンチマーク結果に基づく概算値です。実際の性能は環境・データにより異なります。

ライブラリ 検索速度 構築速度 メモリ効率
ahocorasick_rs 1.5〜7× 高速 同等〜やや遅い 同等
pyahocorasick 1.0(基準) 1.0 1.0
flashtext 0.3〜1.0× 遅い 低い
ahocorapy 0.1〜0.3× 遅い 低い
regex (1000+ keywords) 0.01〜0.1× N/A N/A

キーワード数と性能の関係

キーワード数 regex pyahocorasick ahocorasick_rs
10 良好 良好 良好
100 良好 良好 良好
500 低下開始 良好 良好
1,000+ 急激に低下 安定 安定
10,000+ 使用不可 安定 安定

技術選定について

これらがある中で以下のようになります

                    キーワード数
                    ↓
    ┌───────────────┴───────────────┐
    │                               │
  〜100個                        100個以上
    │                               │
    ▼                               ▼
  regex/re              値の関連付けが必要?
                        ↓
            ┌───────────┴───────────┐
            │                       │
           必要                   不要
            │                       │
            ▼                       ▼
    pyahocorasick            ahocorasick_rs
    (バランス良好)            (最速)

ここで キーワードに値を関連付けたい : 値の関連付けがキーワードになってきます。これは以下のような用途・例の場合に適当することができそうです

以下のようにキーワードに任意のデータを紐付けできるため用途は以下の場合を想定できます

  • 固有表現抽出(NER)
  • 辞書ベース変換(読み仮名・翻訳)
  • コンテンツフィルタリング
  • ログ解析・アラート
automaton.add_word("東京", {"reading": "とうきょう", "en": "Tokyo"})