AIの業務活用

AIが作成した正規表現を一致・不一致の例でテストする

AIが生成した正規表現は完成したルールではなく、まず下書きとして扱います。小さな合成テスト表を作り、期待結果と実際の一致結果を比較し、パターンを修正してから実データで使う前にレビュー結果を保存します。

目次を表示

この翻訳はAIで作成しました。コード、単位、数値は原文と併せて確認してください。各言語のネイティブ話者による校閲は、まだ完了していません。 English

対象読者AIを使って正規表現の下書きを作成し、既知の一致例と不一致例で簡単に検証したい人向けです。

準備するもの
  • Python 3.12と、そのバージョンを起動できるターミナルコマンドが必要です。
  • UTF-8のCSVファイルとPythonファイルを保存できるテキストエディタが必要です。
  • スクリプトがoutputs配下に新しいフォルダを作成できる作業フォルダが必要です。
  • 必要なのはPython標準ライブラリのcsv、pathlib、reだけです。

01正規表現をテストする前に要件を書く

社内の作業IDがWK-YYYY-NNNNという形式でなければならないとします。接頭辞は大文字のWK、年は2000から2099、区切り文字はハイフン、最後の部分は正確に4桁のASCII数字である必要があります。

AIアシスタントがWK-\d{4}-\d{4}というパターンを提案したとします。一見もっともらしく見えますが、年については4桁の数字であることしか要求していません。指定された2000-2099の範囲は強制されません。最も安全な次の手順は、すぐに使うのではなく、正解が分かっている例でテストすることです。

02合成テストケースを作成する

以下のデータセットは合成データで、このページ専用に作成したものです。regex_cases.csvとして保存してください。一致すべき文字列が3件、一致すべきでない文字列が7件含まれています。

csv
case_id,value,should_match
C001,WK-2026-0001,yes
C002,WK-2000-0042,yes
C003,WK-2099-9999,yes
C004,WK-1999-0001,no
C005,wk-2026-0001,no
C006,WK-2026-001,no
C007,WK-2026-00001,no
C008,WK-2026_0001,no
C009," WK-2026-0001",no
C010,WK-20A6-0001,no

テストケースには、許可される年の境界、範囲外の年、小文字の接頭辞、短すぎる・長すぎる連番、誤った区切り文字、先頭の空白、年の中の数字以外の文字が含まれています。正規表現を実行する前に、人手で分類できるほど小さな例です。

03AIのパターンがどこで間違うかを予測する

AIのパターンWK-\d{4}-\d{4}はC001、C002、C003を受け入れるはずです。C005からC010までは構造上の理由で拒否されるはずです。ただし1999も4桁の数字なので、C004も誤って受け入れてしまいます。

ケース期待結果AIパターン結果
C001MATCHMATCHPASS
C002MATCHMATCHPASS
C003MATCHMATCHPASS
C004NO MATCHMATCHFAIL
C005NO MATCHNO MATCHPASS
C006NO MATCHNO MATCHPASS
C007NO MATCHNO MATCHPASS
C008NO MATCHNO MATCHPASS
C009NO MATCHNO MATCHPASS
C010NO MATCHNO MATCHPASS

修正版のパターンはWK-20[0-9]{2}-[0-9]{4}です。先頭の20によって年を2000から2099に制限し、[0-9]によって意図した数字集合を明示します。スクリプトはfullmatchを使うため、入力文字列全体がパターンを満たす必要があります。

04期待結果と実際の一致結果を自動比較する

以下のスクリプトをai_regex_check.pyとして保存してください。AIの提案パターンと修正版パターンの両方をテストし、すべてのケースをCSVレポートに書き出し、既存の出力フォルダは再利用しません。

python
import csv
import re
from pathlib import Path

SOURCE = Path("regex_cases.csv")
OUTPUT_DIR = Path("outputs") / "regex_check_result"
REPORT = OUTPUT_DIR / "regex_check.csv"

AI_PATTERN = re.compile(r"WK-\d{4}-\d{4}")
REVISED_PATTERN = re.compile(r"WK-20[0-9]{2}-[0-9]{4}")


def parse_expected(value: str) -> bool:
    normalized = value.strip().lower()
    if normalized == "yes":
        return True
    if normalized == "no":
        return False
    raise ValueError(f"Expected yes or no, got: {value!r}")


def label(value: bool) -> str:
    return "MATCH" if value else "NO MATCH"


def main() -> None:
    if not SOURCE.is_file():
        raise FileNotFoundError(f"Source CSV not found: {SOURCE}")
    if OUTPUT_DIR.exists():
        raise FileExistsError(f"Output folder already exists: {OUTPUT_DIR}")

    results = []
    ai_failures = 0
    revised_failures = 0

    with SOURCE.open("r", encoding="utf-8-sig", newline="") as stream:
        reader = csv.DictReader(stream)
        required = {"case_id", "value", "should_match"}
        if reader.fieldnames is None or not required.issubset(reader.fieldnames):
            raise ValueError("CSV is missing a required column.")

        for row_number, row in enumerate(reader, start=2):
            expected = parse_expected(row["should_match"])
            text = row["value"]
            ai_actual = AI_PATTERN.fullmatch(text) is not None
            revised_actual = REVISED_PATTERN.fullmatch(text) is not None
            ai_pass = ai_actual == expected
            revised_pass = revised_actual == expected

            if not ai_pass:
                ai_failures += 1
            if not revised_pass:
                revised_failures += 1

            results.append({
                "case_id": row["case_id"],
                "value": text,
                "expected": label(expected),
                "ai_actual": label(ai_actual),
                "ai_test": "PASS" if ai_pass else "FAIL",
                "revised_actual": label(revised_actual),
                "revised_test": "PASS" if revised_pass else "FAIL",
            })

    OUTPUT_DIR.parent.mkdir(parents=True, exist_ok=True)
    OUTPUT_DIR.mkdir()
    fields = [
        "case_id", "value", "expected", "ai_actual", "ai_test",
        "revised_actual", "revised_test"
    ]
    with REPORT.open("x", encoding="utf-8", newline="") as stream:
        writer = csv.DictWriter(stream, fieldnames=fields)
        writer.writeheader()
        writer.writerows(results)

    print(f"Cases: {len(results)}.")
    print(f"AI pattern failures: {ai_failures}.")
    print(f"Revised pattern failures: {revised_failures}.")
    print(f"Report: {REPORT.as_posix()}")

    if revised_failures:
        raise RuntimeError("Revised pattern still fails one or more tests.")


if __name__ == "__main__":
    main()
text
python ai_regex_check.py

05期待されるレポートを確認する

テストケースは10件です。AIのパターンはC004の1件だけで失敗し、修正版パターンは10件すべてに合格するはずです。

項目期待値
ケース数10
AIパターンの失敗数1
修正版パターンの失敗数0
AIパターンの失敗ケースC004
修正版パターンの合格ケース数10

以下の期待コンソール出力は、テスト表とスクリプトから手作業で導いたものです。実際の実行ログではありません。

text
Cases: 10.
AI pattern failures: 1.
Revised pattern failures: 0.
Report: outputs/regex_check_result/regex_check.csv

06境界ケースを追加し、よくあるミスを確認する

  • この例の2000と2099のように、有効範囲の最小値と最大値を含めます。
  • 1999のように、境界のすぐ外側の値を少なくとも1つ含めます。
  • 大文字・小文字の違い、区切り文字、フィールド長、空白、不正な文字をテストします。
  • 文字列全体が形式に一致する必要がある要件ではfullmatchを使います。
  • OUTPUT_DIRを変更せずにスクリプトを再実行します。以前のレポートを上書きせず、FileExistsErrorで停止するはずです。
ミスなぜ重要か
一致すべき文字列だけをテストする緩すぎる正規表現でも、不一致例を試すまでは正しく見えることがあります。
要件を言い直さずにAIのパターンを使う意図した問題とは少し異なる問題を解くパターンになっている可能性があります。
通常の例を1つだけ確認する境界、長さ、大文字・小文字、区切り文字の誤りが未検証のまま残ります。
フィールド全体のルールなのにfullmatchではなくsearchを使う全体としては無効な値の中に、有効に見える部分文字列が見つかることがあります。
正規表現の出力を見た後で期待結果を変更するテストが必要な動作を独立して定義する役割を失います。

07正規表現テストでは証明できないことを理解する

この10件のテストに合格しても、修正版パターンがこの10件の合成例で正しく動くことしか示しません。考えられるすべての入力を正しく処理できることを数学的に証明するものではありません。新しい境界条件や失敗パターンが見つかったら、テストケースを追加してください。

正規表現が検証するのは構文であり、業務上の事実ではありません。WK-2026-1234が完全に一致しても、そのIDが実際にデータベースに存在するとは限りません。存在確認、一意性、権限、他フィールドとの関係は別途確認が必要です。

この小さな例では、非常に長い入力や攻撃的に作られた入力に対する性能は評価していません。より複雑なAI生成パターンを信頼できないテキストを処理するサービスに組み込む前に、正確性と実行時の挙動の両方を確認してください。

実行・検証の記録

2026-09-20 · 手作業で確認した例 · 対象: Python 3.12 · 標準ライブラリ: csv, pathlib, re · 未実行

  • 合成値3件を一致する期待値、7件を一致しない期待値として手作業で分類しました。
  • 1999が4桁の年条件を満たすため、AIパターンがC004を受け入れることを手作業で確認しました。
  • AIパターンの失敗が10件中1件であることを手作業で確認しました。
  • 修正版の20[0-9]{2}が2000から2099を受け入れ、例に含まれる1999を拒否することを手作業で確認しました。
  • fullmatchの使用、期待値と実際値の比較、出力衝突の防止、レポート生成をスクリプトで確認しました。
  • 期待される失敗件数とコンソール出力を手作業で導きました。
検証範囲の限界
  • この回答の作成者はコードを実行しておらず、Pythonの正規表現動作やファイルシステム出力はここではテストしていません。
  • 掲載した10件の合成ケースだけを手作業で評価しており、網羅的な正確性は確認していません。
  • 非常に長い文字列や攻撃的に作られた文字列での性能はテストしていません。
  • 公式ドキュメントのURLは既知の文書位置を使用していますが、リアルタイムでは確認していません。

サイト全体の執筆・検証方針

参考資料

説明と例は独自に作成しました。関連する動作や概念は、以下の公式資料で確認できます。