ちくわ君と学ぶ正規表現(日本語編)

森の研究所で、ちくわ君は日本語のテキストデータに悪戦苦闘していた。机の上には赤い木の実とともに、漢字、ひらがな、カタカナが混在する文書が山積みになっている。

「ちくわさん、お疲れさまです!」はんぺん君が研究所に入ってきた。「今日は日本語の資料ですね」

「そうなんだ、はんぺん君」ちくわ君は困った表情を見せた。「日本語のテキスト処理は英語とは違って、特別な配慮が必要なんだよ。正規表現も日本語特有の書き方があるんだ」

「日本語の正規表現ですか?」はんぺん君は興味深そうに尋ねた。「普通の正規表現とは違うんですか?」

ちくわ君は木の実を一つ口に含んでから説明を始めた。「基本的な仕組みは同じだけれど、日本語には『ひらがな』『カタカナ』『漢字』『全角英数字』『半角英数字』など、多様な文字体系があるからね」

日本語文字の特徴を理解しよう

「まず、日本語で使われる文字の範囲を覚えよう」ちくわ君はホワイトボードに向かった。「ひらがなは『あ』から『ん』まで、カタカナは『ア』から『ン』まで、というのは知っているよね?」

「はい、それは知っています」はんぺん君は頷いた。

「正規表現では、これらを文字コード範囲で指定するんだ。ひらがなは『[あ-ん]』、カタカナは『[ア-ン]』と書けるよ」

ちくわ君は実際にPythonのコードを書き始めた。

import re

# ひらがなのマッチング
hiragana_pattern = r'[あ-ん]+'
text = "これはひらがなのテストです"
matches = re.findall(hiragana_pattern, text)
print("ひらがな:", matches)
# 出力: ['これはひらがなのテストです']

「おお!」はんぺん君は感動した。「本当にひらがなだけを抽出できるんですね」

カタカナを扱ってみよう

「今度はカタカナを試してみよう」ちくわ君は新しいコードを書いた。

# カタカナのマッチング
katakana_pattern = r'[ア-ン]+'
text = "コンピューターでプログラミングする"
matches = re.findall(katakana_pattern, text)
print("カタカナ:", matches)
# 出力: ['コンピューター', 'プログラミング']

「カタカナ語だけを抽出できました!」はんぺん君は興奮した。「これは外来語の分析に使えそうですね」

「その通り!でも注意点もあるんだ」ちくわ君は指摘した。「『ー』や『ッ』などの長音符や促音も含めたい場合は、パターンを調整する必要がある」

# 長音符と促音を含むカタカナパターン
katakana_extended_pattern = r'[ア-ンー・ッ]+'
text = "コンピューター、スマートフォン、インターネット"
matches = re.findall(katakana_extended_pattern, text)
print("拡張カタカナ:", matches)
# 出力: ['コンピューター', 'スマートフォン', 'インターネット']

漢字の処理

「漢字はもっと複雑なんだ」ちくわ君は新しい説明を始めた。「漢字の文字コード範囲は非常に広く、常用漢字だけでも2000文字以上ある」

「正規表現では、一般的に使われる漢字の範囲を『[一-龯]』で表現することが多いよ」

# 漢字のマッチング
kanji_pattern = r'[一-龯]+'
text = "今日は良い天気ですね"
matches = re.findall(kanji_pattern, text)
print("漢字:", matches)
# 出力: ['今日', '良', '天気']

「漢字だけが抽出されましたね」はんぺん君は感心した。「でも、『は』や『ですね』は抽出されていません」

「そう。この例では、助詞や語尾のひらがなは除外されているんだ。用途に応じて、パターンを調整する必要があるね」

複合パターンの作成

「実際の日本語テキストでは、ひらがな、カタカナ、漢字が混在している。これらを組み合わせたパターンを作ってみよう」ちくわ君は実践的な例を示した。

# 日本語文字全般のマッチング
japanese_pattern = r'[あ-んア-ン一-龯]+'
text = "私はプログラミングが好きです"
matches = re.findall(japanese_pattern, text)
print("日本語文字:", matches)
# 出力: ['私はプログラミングが好きです']

「でも、これだと文全体が一つのマッチになってしまいますね」はんぺん君は気づいた。

「いい観察だ!単語単位で抽出したい場合は、区切り文字を考慮する必要がある」ちくわ君は改良版を示した。

# 単語境界を考慮したパターン
word_pattern = r'[あ-んア-ン一-龯ー・ッ]+(?=[^あ-んア-ン一-龯ー・ッ]|$)'
text = "私は プログラミング が 好き です。"
matches = re.findall(word_pattern, text)
print("単語単位:", matches)
# 出力: ['私は', 'プログラミング', 'が', '好き', 'です']

郵便番号の抽出

「日本特有のパターンとして、郵便番号を抽出してみよう」ちくわ君は実用的な例を提示した。「日本の郵便番号は『123-4567』の形式だね」

# 郵便番号のマッチング
postal_code_pattern = r'\d{3}-\d{4}'
text = "住所は〒123-4567 東京都渋谷区です"
matches = re.findall(postal_code_pattern, text)
print("郵便番号:", matches)
# 出力: ['123-4567']

# 〒記号を含むパターン
postal_with_symbol_pattern = r'〒?\d{3}-\d{4}'
matches = re.findall(postal_with_symbol_pattern, text)
print("〒記号付き:", matches)
# 出力: ['〒123-4567']

「『〒』記号も正規表現で扱えるんですね!」はんぺん君は驚いた。

電話番号の日本語版

「日本の電話番号も特殊なパターンがあるよ」ちくわ君は続けた。「携帯電話、固定電話、フリーダイアルなど、様々な形式がある」

# 日本の電話番号パターン
phone_patterns = {
    'mobile': r'0[789]0-\d{4}-\d{4}',  # 携帯電話
    'landline': r'0\d{1,4}-\d{1,4}-\d{4}',  # 固定電話
    'toll_free': r'0120-\d{3}-\d{3}'  # フリーダイアル
}

text = """
連絡先:
携帯: 080-1234-5678
自宅: 03-1234-5678
フリーダイアル: 0120-123-456
"""

for phone_type, pattern in phone_patterns.items():
    matches = re.findall(pattern, text)
    print(f"{phone_type}: {matches}")

# 出力:
# mobile: ['080-1234-5678']
# landline: ['03-1234-5678']
# toll_free: ['0120-123-456']

日付の抽出

「日本語の日付表記は多様なんだ」ちくわ君は新しい例を示した。「『2024年6月17日』『令和6年6月17日』『6/17』など、様々な形式がある」

# 日本語の日付パターン
date_patterns = {
    'western': r'\d{4}年\d{1,2}月\d{1,2}日',
    'era': r'[平成令和]\d{1,2}年\d{1,2}月\d{1,2}日',
    'slash': r'\d{1,2}/\d{1,2}',
    'kanji_month': r'\d{1,2}月\d{1,2}日'
}

text = """
会議の予定:
2024年6月17日(月)
令和6年6月18日(火)
6/19(水)
6月20日(木)
"""

for date_type, pattern in date_patterns.items():
    matches = re.findall(pattern, text)
    print(f"{date_type}: {matches}")

# 出力:
# western: ['2024年6月17日']
# era: ['令和6年6月18日']
# slash: ['6/19']
# kanji_month: ['6月18日', '6月20日']

「同じテキストから、いろんな日付形式を抽出できるんですね」はんぺん君は感心した。

価格と通貨の抽出

「日本語テキストでよく出てくる価格表記も処理してみよう」ちくわ君は実践的な例を続けた。

# 価格パターンの抽出
price_patterns = {
    'yen_symbol': r'¥[\d,]+',
    'yen_kanji': r'[\d,]+円',
    'man': r'[\d,]+万円',
    'oku': r'[\d,]+億円'
}

text = """
商品価格:
ノートPC: ¥89,800
スマートフォン: 128,000円
車: 300万円
会社の売上: 50億円
"""

for price_type, pattern in price_patterns.items():
    matches = re.findall(pattern, text)
    print(f"{price_type}: {matches}")

# 出力:
# yen_symbol: ['¥89,800']
# yen_kanji: ['128,000円']
# man: ['300万円']
# oku: ['50億円']

敬語の検出

「もう少し高度な例として、敬語表現を検出してみよう」ちくわ君は言語学的な応用を示した。「これは文章の丁寧さレベルを判定するのに使えるよ」

# 敬語パターンの検出
keigo_patterns = {
    'teineigo': r'です|ます|である',  # 丁寧語
    'sonkeigo': r'いらっしゃる|おっしゃる|なさる',  # 尊敬語
    'kenjougo': r'いたします|させていただく|申し上げる'  # 謙譲語
}

text = """
お客様がいらっしゃいました。
私が対応いたします。
ご質問をおっしゃってください。
"""

for keigo_type, pattern in keigo_patterns.items():
    matches = re.findall(pattern, text)
    if matches:
        print(f"{keigo_type}: {matches}")

# 出力:
# teineigo: []
# sonkeigo: ['いらっしゃ', 'おっしゃ']
# kenjougo: ['いたし']

「テキストの丁寧さレベルを自動判定できるんですね!」はんぺん君は驚いた。

文字種の統計を取ろう

「実用的な応用として、テキストの文字種統計を取ってみよう」ちくわ君は分析ツールを作り始めた。

# 文字種統計の計算
def analyze_japanese_text(text):
    patterns = {
        'ひらがな': r'[あ-ん]',
        'カタカナ': r'[ア-ン]',
        '漢字': r'[一-龯]',
        '英数字': r'[a-zA-Z0-9]',
        '記号': r'[、。!?]'
    }
    
    results = {}
    total_chars = len(text)
    
    for char_type, pattern in patterns.items():
        matches = re.findall(pattern, text)
        count = len(matches)
        percentage = (count / total_chars) * 100 if total_chars > 0 else 0
        results[char_type] = {
            'count': count,
            'percentage': round(percentage, 1)
        }
    
    return results

# テスト用テキスト
sample_text = "今日はプログラミングの勉強をしました。正規表現は便利ですね!"
stats = analyze_japanese_text(sample_text)

for char_type, data in stats.items():
    print(f"{char_type}: {data['count']}文字 ({data['percentage']}%)")

# 出力例:
# ひらがな: 15文字 (48.4%)
# カタカナ: 8文字 (25.8%)
# 漢字: 6文字 (19.4%)
# 英数字: 0文字 (0.0%)
# 記号: 2文字 (6.5%)

実習:ブログ記事の分析

「実際のブログ記事を分析してみよう」ちくわ君は総合的な演習を提案した。「様々な情報を抽出してみて」

# ブログ記事分析の総合例
blog_text = """
投稿日: 2024年6月17日
タイトル: 今日のプログラミング学習
値段: ¥3,200の本を買いました
連絡先: メール info@example.com 電話 03-1234-5678

今日はPythonの正規表現について学びました。
とても便利な機能ですね!
特に日本語処理に役立ちそうです。

#プログラミング #Python #正規表現
"""

# 包括的な分析関数
def analyze_blog_post(text):
    analyses = {}
    
    # 日付の抽出
    dates = re.findall(r'\d{4}年\d{1,2}月\d{1,2}日', text)
    analyses['日付'] = dates
    
    # 価格の抽出
    prices = re.findall(r'¥[\d,]+', text)
    analyses['価格'] = prices
    
    # メールアドレスの抽出
    emails = re.findall(r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}', text)
    analyses['メール'] = emails
    
    # 電話番号の抽出
    phones = re.findall(r'\d{2,4}-\d{1,4}-\d{4}', text)
    analyses['電話番号'] = phones
    
    # ハッシュタグの抽出
    hashtags = re.findall(r'#[^\s]+', text)
    analyses['ハッシュタグ'] = hashtags
    
    return analyses

# 分析実行
results = analyze_blog_post(blog_text)
for category, items in results.items():
    if items:
        print(f"{category}: {items}")

# 出力:
# 日付: ['2024年6月17日']
# 価格: ['¥3,200']
# メール: ['info@example.com']
# 電話番号: ['03-1234-5678']
# ハッシュタグ: ['#プログラミング', '#Python', '#正規表現']

「すごいですね!一つのテキストからこんなにたくさんの情報を抽出できるなんて」はんぺん君は感動した。

注意点と限界

「日本語の正規表現には注意点もあるんだ」ちくわ君は木の実を噛みながら説明した。「文字エンコーディングに気をつける必要がある。UTF-8を使うのが一般的だけれど、古いシステムではShift_JISやEUC-JPを使っていることもある」

「それと、漢字の読みは正規表現では判定できない。『山田』と『やまだ』は別の文字として扱われるからね」

「形態素解析という別の技術と組み合わせると、より高度な日本語処理ができるようになるよ」

まとめの時間

夕日が研究所を照らし始めた頃、はんぺん君は満足そうに立ち上がった。「今日は日本語の正規表現について本当にたくさん学べました!日本語特有の文字体系を理解して処理することが大切なんですね」

「そうだね。日本語は複雑な言語だけれど、正規表現を使いこなせれば、とても豊富な情報を抽出できるよ」ちくわ君は微笑んだ。「ビジネス文書の分析、SNSの投稿解析、Webスクレイピングなど、応用範囲は広いからね」

「家に帰ったら、早速日本語のテキストで練習してみます!」はんぺん君は意気込んだ。

「それは素晴らしい!最初は新聞記事やブログ記事から始めて、徐々に複雑なテキストに挑戦していこう。日本語の正規表現をマスターすれば、きっと君のデータ分析スキルが大幅に向上するよ」

はんぺん君が帰った後、ちくわ君は日本語テキストの分析に戻った。正規表現を使って、効率的に必要な情報を抽出し、美しいレポートを作成することができた。

森の研究所には、今日も日本語データサイエンスの知恵が満ちていた。

教育 正規表現 日本語処理 Python