CASE-009
グレップ・アンド・コー データ探偵事務所
依頼書
依頼人
ハン・ビョル所長
証拠品
事務所の古い金庫、新しい暗証番号の候補6つ、金庫の中の帳簿。
解決条件
新しい暗証番号のルール(8文字以上、数字・小文字・記号を1つずつ)を1つのパターンで検査し、帳簿の金額に3桁ごとのカンマを入れる。
所長が部屋の隅にある古い金庫の前にしゃがみ込みます。「バックトラッカーが金庫の話を持ち出したのは、ただの脅しだよ。それでも暗証番号は今日変える。今度はルールをきちんと守って」
新しい暗証番号のルールは3つ。順番は問いません。
규칙 1. 8자 이상
규칙 2. 숫자, 소문자, 특수문자가 각각 하나 이상
규칙 3. 이 모든 걸 정규식 하나로 검사한다
후보:
vault2026!
vault2026
VAULT2026!
v!2
longpassword!
safe#2026box所長のメモは韓国語です。ルール1は「8文字以上」、ルール2は「数字、小文字、記号がそれぞれ1つ以上」、ルール3は「これらすべてを1つの正規表現で検査する」。その下の「후보」は「候補」です。
カードに書かれていた言葉がよみがえります。「すべて同じ位置で確かめられるのならね」。今日学ぶ文法は、まさにそれです。
4限目の ^ $ \b を思い出してください。これらは文字を1つも食べずに、「ここが先頭か」「ここが単語の境界か」という位置だけを確かめていました。こうしたマッチを幅ゼロ(zero-width)のマッチと呼びます。
re.findall(r"\b", "hi yo") # ['', '', '', '']
re.sub(r"\b", "|", "hi yo") # '|hi| |yo|'\b が見つけたのは空文字列4つ、つまり位置4つです。今日学ぶ先読み・後読みは、この「位置の確認」を自分の好きな条件で作る文法です。
X(?=Y) は「後ろに Y が来る X」を探します。Y は確かめるだけで、マッチには含めません。これを先読み(lookahead)と呼びます。
re.findall(r"\d+(?=원)", "3000원짜리 5개, 합계 15000원") # ['3000', '15000']文字列は韓国語で「3000ウォンのものを5個、合計15000ウォン」という意味です(원 はウォン、개 は「個」)。5개 の 5 は後ろに 원 がないので外れ、見つかった数字には 원 が含まれていません。「ウォンで終わる数字」を探しつつ、数字だけを取り出したいときにぴったりです。
証拠分析室
re.search(r"", text)
戻り値
X(?!Y) は逆に、「後ろに Y が来ない X」です。否定先読みと呼びます。
re.findall(r"\b(?!tmp)\w+", "tmp_log report tmpfile final") # ['report', 'final']
re.findall(r"\d{3}(?!\d)", "12345 678") # ['345', '678']tmp で始まらない単語」です。単語の境界のすぐ後ろが tmp でないことを確かめています。暗証番号のルールのように「これも、あれも、それもなければならない」という条件は、先読みを横に並べて書きます。
pw = r"(?=.*\d)(?=.*[a-z])(?=.*[^\w\s]).{8,}"| かけら | 確かめること |
|---|---|
(?=.*\d) | どこかに数字がある |
(?=.*[a-z]) | どこかに小文字がある |
(?=.*[^\w\s]) | どこかに記号(文字・数字・アンダースコア・空白以外のもの)がある |
.{8,} | 実際に8文字以上を食べる |
3つの先読みは、どれも**同じ位置(先頭)**で検査します。文字を食べないので、1つ終わっても位置はそのまま。次の条件もまた先頭から始まります。だから、つなげると「かつ(AND)」になるのです。
cands = ["vault2026!", "vault2026", "VAULT2026!", "v!2", "longpassword!", "safe#2026box"]
[c for c in cands if re.fullmatch(pw, c)] # ['vault2026!', 'safe#2026box']証拠分析室
re.search(r"", text)
戻り値
ドットのひとこと
先読みは、前をちらっと見るだけで足は動かさないんです。ぼくも窓枠に座って外をながめるだけで、越えはしませんからね。だから何回のぞいても、その場から動いていないんですよ。
向きを逆にすると後読み(lookbehind)です。(?<=Y)X は「前に Y がある X」、(?<!Y)X は「前に Y がない X」です。
re.findall(r"(?<=\$)\d+", "$30 and 40 and $5") # ['30', '5']
re.findall(r"(?<!\$)\b\d+", "$30 and 40 and $5") # ['40']後読みには制約が1つあります。Python の re では、振り返る長さが決まっていなければなりません。
re.compile(r"(?<=a+)b") # PatternError: look-behind requires fixed-width pattern
re.compile(r"(?<=ab|cd)e") # OK:選択肢はどちらも2文字
re.compile(r"(?<=a|bc)d") # PatternError: look-behind requires fixed-width pattern選択肢が複数あっても、長さがすべて同じなら大丈夫です。長さが違うなら、後読みを2つに分けて (?:(?<=a)|(?<=bc))d のように別々に囲みます。
証拠分析室
re.search(r"", text)
戻り値
金庫の中の帳簿は、数字が 1234567 のようにくっついていて読みにくいです。カンマを入れる位置を探して、そこにカンマを入れればよいのです。位置を探す仕事なので、先読み・後読みがうってつけです。
re.sub(r"(?<=\d)(?=(?:\d{3})+(?!\d))", ",", "1234567 and 89 and 1000")
# '1,234,567 and 89 and 1,000'読み方はこうです。「前に数字があり((?<=\d))、後ろに3桁の数字のまとまりが1つ以上続いて((?:\d{3})+)、そこで数字が終わる((?!\d))位置」。その位置は文字ではないので、sub は文字を消さずにカンマだけを差し込みます。
先読みの中にグループを入れると、重なり合うマッチも見つけられます。位置だけを1つずつずらしながら、その場所の2文字をグループで取り出すコツです。
re.findall(r"(?=(\w\w))", "abc") # ['ab', 'bc']証拠分析室
re.search(r"", text)
戻り値
検査器を仕上げて、候補をふるいにかけます。
pw = re.compile(r"(?=.*\d)(?=.*[a-z])(?=.*[^\w\s]).{8,}")
for c in cands:
print(f"{c:<14} {'통과' if pw.fullmatch(c) else '탈락'}")
# vault2026! 통과
# vault2026 탈락
# VAULT2026! 탈락
# v!2 탈락
# longpassword! 탈락
# safe#2026box 통과事務所の道具なので、表示は韓国語のままです(통과 は「合格」、탈락 は「不合格」)。
所長は、合格した2つのどちらも選びません。「候補リストに書かれた時点で、もう暗証番号じゃない」。そう言って、ルールを満たす新しい暗証番号を誰にも見られないよう自分で決め、検査器だけを事務所の共用道具箱に入れておきます。
事件解決報告書 — 金庫の暗証番号のルールを、3つの先読みで検査するツールを作成。候補6つのうち、ルールを満たしたのは2つだけ。暗証番号は所長が別途変更した。帳簿の金額には3桁ごとのカンマを入れて整理した。
"price: 1200won, tax: 120won" から、won の前の数字だけを先読みで取り出してみましょう。re.findall(r"(?<!\d)\d{2}(?!\d)", "1 22 333 44") の結果を予想してみましょう。なぜ 333 の一部は出てこないのでしょうか。バックトラッカーの痕跡
(a+)+$
金庫はそのままにしておこう。代わりに、警察署のカン班長に手紙を3通送った。同じ人間が書いたと証明できるかな。癖は文字に残る。
所長の手帳
先読み・後読みは幅ゼロのマッチ。文字を食べずに位置だけを確かめる(^ $ \b と同じ仲間)。
X(?=Y) は後ろに Y が来る X、X(?!Y) は後ろに Y が来ない X。
(?<=Y)X は前に Y がある X、(?<!Y)X は前に Y がない X。後読みは固定長のみ。
先読みをつなげると AND:(?=.*\d)(?=.*[a-z])(?=.*[^\w\s]).{8,}
位置を探して sub すると、文字を消さずに差し込める:3桁ごとのカンマ。
(?=(\w\w)) のように先読みの中のグループで、重なり合うマッチを取り出せる。
新しい授業・講座のお知らせを受け取る
この講座に授業が追加されたり、続きの講座が公開されたりしたらメールでお知らせします。
R
ReflexKR
触って学ぶPythonとReflex。すべての例はReflex 0.10.0で実際に動かして確認しています。
ReflexKRはバナー広告なし・無料で運営しています。役に立ったら、コーヒー一杯で応援してください。 ☕ コーヒーで応援する
アフィリエイトと支援について© 2026 ReflexKR · Since 2026 · CC BY-NC 4.0
質問と回答
この回について気になることを書き込んでください。すべての言語のページの質問がここに集まります。