from typing import Any import fugashi from config import Config try: # SudachiPy があれば直接利用してモードCを使用 from sudachipy import dictionary as sudachi_dictionary from sudachipy import tokenizer as sudachi_tokenizer _SUDACHI_AVAILABLE = True except Exception: _SUDACHI_AVAILABLE = False class WordCounter: """単語数を数えるクラス(SudachiPyがあれば mode=C、なければfugashi)""" def __init__(self, tokenizer: Any = None): """ 初期化 Args: tokenizer: fugashiトークナイザー(Noneの場合はデフォルトを使用) """ # 優先順位: 引数tokenizer > SudachiPy > fugashi(GenericTagger) self._use_sudachi = False self._sudachi_mode = None if tokenizer is not None: self.tokenizer = tokenizer elif _SUDACHI_AVAILABLE: # SudachiPyの辞書は自動で同梱辞書を参照(sudachidict_core) # 外部設定不要。SplitMode.C を使用 self._use_sudachi = True self.tokenizer = sudachi_dictionary.Dictionary().create() self._sudachi_mode = sudachi_tokenizer.Tokenizer.SplitMode.C else: # fugashi (MeCab) フォールバック self.tokenizer = fugashi.GenericTagger(Config.get_fugashi_args()) def count_words(self, text: str) -> int: """ テキストの単語数をカウント Args: text: カウントするテキスト Returns: int: 単語数 """ if not text: return 0 try: # fugashiで形態素解析して単語数をカウント if self._use_sudachi: tokens = self.tokenizer.tokenize(text, self._sudachi_mode) return len(tokens) else: tokens = self.tokenizer(text) return len(tokens) except Exception as e: print(f"fugashi単語数カウントエラー: {e}") # フォールバック: 空白で分割 return len(text.split()) def is_word_boundary(self, text: str, position: int) -> bool: """ 指定位置が単語境界かどうかを判定 Args: text: テキスト position: 位置(負の値で末尾から指定可能、-1は末尾) Returns: bool: 単語境界かどうか """ if not text: return True # 負のインデックスを正のインデックスに変換 if position < 0: position = len(text) + position if position >= len(text): return True try: # fugashiで形態素解析 if self._use_sudachi: tokens = self.tokenizer.tokenize(text, self._sudachi_mode) surfaces = [m.surface() for m in tokens] else: tokens = self.tokenizer(text) surfaces = [m.surface for m in tokens] current_pos = 0 for surface in surfaces: token_length = len(surface) if current_pos <= position < current_pos + token_length: return False if position == current_pos + token_length: return True current_pos += token_length return True except Exception as e: print(f"fugashi境界判定エラー: {e}") # フォールバック: 空白文字で判定 return position < len(text) and text[position].isspace() # テスト関数 def test_word_counter(): """WordCounterのテスト""" print("=== WordCounterテスト ===") try: counter = WordCounter() # 基本的な単語数カウントテスト print("単語数カウントテスト:") test_texts = [ "私はエ", "私はエジ", "私はエジソ", "私はエジソン", "私はエジソンで" ] for text in test_texts: word_count = counter.count_words(text) print(f" '{text}' → {word_count}語") # 単語境界テスト print("\n単語境界テスト:") test_text = "私はエジソンで" print(f" '{test_text}' の境界判定:") for i in range(len(test_text) + 1): is_boundary = counter.is_word_boundary(test_text, i) print(f" 位置{i}: {is_boundary}") # 負のインデックステスト print("\n負のインデックステスト:") print(f" '{test_text}' の負のインデックス境界判定:") for i in range(-len(test_text), 1): is_boundary = counter.is_word_boundary(test_text, i) print(f" 位置{i}: {is_boundary}") # Sudachi(C) と fugashi(IPA) の分割比較 print("\n分割比較: Sudachi(C) vs fugashi(IPA)") compare_texts = [ "私はエジソンで有名な科学者です。", "電球を作ったのは誰?", "自然言語処理は面白い。", "電球を" ] # 準備: 各トークナイザ sudachi_ok = False sudachi_tok = None sudachi_mode = None try: if _SUDACHI_AVAILABLE: sudachi_tok = sudachi_dictionary.Dictionary().create() sudachi_mode = sudachi_tokenizer.Tokenizer.SplitMode.C sudachi_ok = True except Exception: sudachi_ok = False ipa_tagger = fugashi.GenericTagger(Config.get_fugashi_args()) for text in compare_texts: print(f"\n--- テキスト: {text}") # fugashi(IPA) try: ipa_tokens = ipa_tagger(text) ipa_surfaces = [t.surface for t in ipa_tokens] print(f"fugashi(IPA) {len(ipa_surfaces)}語: {' | '.join(ipa_surfaces)}") except Exception as e: print(f"fugashi(IPA) 解析失敗: {e}") # Sudachi(C) if sudachi_ok: try: s_tokens = sudachi_tok.tokenize(text, sudachi_mode) s_surfaces = [m.surface() for m in s_tokens] print(f"Sudachi(C) {len(s_surfaces)}語: {' | '.join(s_surfaces)}") except Exception as e: print(f"Sudachi(C) 解析失敗: {e}") else: print("Sudachi(C) は利用不可(モジュール未検出)") print("\nテスト完了") except ImportError: print("fugashiがインストールされていません。pip install fugashi でインストールしてください。") except Exception as e: print(f"テストエラー: {e}") if __name__ == "__main__": test_word_counter()