R

ReflexKR

Reflex公式ドキュメント
目で見て覚える正規表現
5限目

時間 3:20–4:10

バラバラな日付:グループと選択

CASE-005

グレップ・アンド・コー データ探偵事務所

依頼書

バラバラな日付

依頼人

ネットショップ「マーケット・チョン」のチョン社長

証拠品

マーケット・チョンの注文記録。アルバイトごとに日付をばらばらの形で書いている。

解決条件

すべての注文から年・月・日を別々に取り出し、同じ形にそろえる。

偽の会員登録事件の翌日、チョン社長がまたやってきます。今度はまずため息からです。「登録は防げました。でも注文記録を日付ごとに整理しようとしたら、アルバイトの子が3人とも日付を違う書き方で入れてたんですよ」所長がコーヒーをひと口すすります。「探し方は第1部で覚えた。次は、見つけたものの中から 取り出す 方法の番だ」

注文記録はこうです。

#1001 2026-10-20 ship=done
#1002 2026.10.20 ship=wait
#1003 2026/10/19 ship=done
#1004 20261019 ship=done
#1005 2026-10/20 ship=wait

日付を見つけるだけでは足りません。「2026」「10」「20」を 別々に 取り出さないと整理できません。今日の道具は丸かっこ、つまり グループ です。

5-1. 丸かっこでくくる:グループに量指定子 (5分)

3限目で、量指定子は直前のひとかたまりにだけ付くと言いました。丸かっこ ( ) でくくると、複数の文字が ひとかたまり になります。

re.fullmatch(r"ha+", "hahaha")      # None
re.fullmatch(r"(ha)+", "hahaha")    # <re.Match object; span=(0, 6), match='hahaha'>

IPアドレスのように「数字とピリオド」が3回くり返される形も、グループに量指定子を付けて書きます。

re.search(r"(\d{1,3}\.){3}\d{1,3}", "ip=203.0.113.66")
# <re.Match object; span=(3, 15), match='203.0.113.66'>

(\d{1,3}\.){3} は「1〜3桁の数字とピリオド」を3回です。最後の数字の後ろにはピリオドがないので、\d{1,3} を別に付けました。

5-2. 選択:| あれかこれか (8分)

| は「左 または 右」です。ところが | は、正規表現の中で 優先順位がいちばん低い のです。丸かっこで区切らないと、パターン全体を2つに割ってしまいます。

re.fullmatch(r"ship=done|wait", "wait")          # <re.Match object; span=(0, 4), match='wait'>
re.fullmatch(r"ship=(done|wait)", "wait")        # None
re.fullmatch(r"ship=(done|wait)", "ship=wait")   # <re.Match object; span=(0, 9), match='ship=wait'>

ship=done|wait は「ship=done または wait」です。「ship= のあとに done または wait」にしたいなら、選ぶ部分だけを丸かっこで囲みます。

アンカーと組み合わせるときは特に注意が必要です。^a|b$ は「aかbだけでできた文字列」ではなく、「^a または b$」です。

re.findall(r"^a|b$", "a.b")         # ['a', 'b']
re.search(r"^ok|no$", "say no")     # <re.Match object; span=(4, 6), match='no'>

say no は ok で始まっていないのにマッチしました。本来の意図どおりにするなら ^(ok|no)$ と書きます。

選択肢は 左から 順に試され、先に成功したものが使われます。いちばん長いものが選ばれるわけではありません。

re.search(r"10|100", "100")   # <re.Match object; span=(0, 2), match='10'>
re.search(r"100|10", "100")   # <re.Match object; span=(0, 3), match='100'>
  • 長い選択肢を前に置きましょう。
  • 1文字同士の選択 -|\.|/ は、クラス [-./] と同じです。こういうときはクラスのほうが短くて速くなります。

証拠分析室

re.search(r"", text)

戻り値

ship=done|wait は「ship=done」または「wait」です。選ぶ部分だけを丸かっこで囲んでみましょう。

5-3. グループ番号で取り出す (8分)

丸かっこには、くくる以外にもうひとつの役目があります。丸かっこの中でマッチした文字を 別に覚えておく のです。これを キャプチャグループ と呼び、開きかっこの順に1、2、3……と番号が付きます。

上の注文記録が orders という文字列に入っているとしましょう。

m = re.search(r"(\d{4})-(\d\d)-(\d\d)", orders)
print(m.group(0))               # 2026-10-20
print(m.group(1))               # 2026
print(m.group(2), m.group(3))   # 10 20
print(m.groups())               # ('2026', '10', '20')
print(m.group(1, 2))            # ('2026', '10')
print(m[1], m[0])               # 2026 2026-10-20
  • group(0)(またはただの group())はマッチ 全体 です。グループは1番からです。
  • groups() は1番から最後までをタプルで返します。
  • group(1, 2) のように番号を複数渡すと、タプルで返ってきます。
  • m[1] は m.group(1) の省略形です。

かっこが入れ子になっていてもルールは同じです。開きかっこ ( が現れる順番 に番号を数えます。

((\d{4})-(\d\d))-(\d\d)
12       3       4
m = re.search(r"((\d{4})-(\d\d))-(\d\d)", "2026-10-20")
print(m.groups())     # ('2026-10', '2026', '10', '20')

証拠分析室

re.search(r"", text)

戻り値

年・月・日をそれぞれ丸かっこで囲んでみましょう。グループごとに色が付き、下の欄に m.group(1)〜(3) が表示されます。

5-4. キャプチャしない丸かっこ:(?:...) (5分)

くくるだけで、覚えておく必要はないこともよくあります。| の範囲を決めたり、量指定子を付けたりするために丸かっこを使うときです。そんなときは (?:...) を使います。くくりはしますが、番号を受け取りません。

m = re.search(r"(?:\d{4})-(\d\d)", "2026-10")
print(m.group(1))                           # 10
print(re.fullmatch(r"(?:ab)+", "abab").groups())   # ()

最初のかっこが番号を受け取らないので、(\d\d) が1番になりました。必要なものだけをキャプチャすれば番号がすっきりし、次の節のfindallの落とし穴も避けられます。

5-5. findall とグループ (8分)

findallは、パターンにキャプチャグループがあると動きが変わります。

キャプチャグループの数findallが返すもの
0個マッチ全体の文字列のリスト
1個そのグループ の文字列のリスト
2個以上グループの タプル のリスト
re.findall(r"\d{4}[-./]\d\d[-./]\d\d", orders)
# ['2026-10-20', '2026.10.20', '2026/10/19', '2026-10/20']
re.findall(r"(\d{4})[-./]\d\d[-./]\d\d", orders)
# ['2026', '2026', '2026', '2026']
re.findall(r"(\d{4})[-./](\d\d)[-./](\d\d)", orders)
# [('2026', '10', '20'), ('2026', '10', '20'), ('2026', '10', '19'), ('2026', '10', '20')]

年・月・日がほしいときは、タプルのリストが便利です。問題は、くくるためだけに使った丸かっこ までグループになってしまうことです。

re.findall(r"\d{4}(-|\.|/)\d\d(-|\.|/)\d\d", orders)
# [('-', '-'), ('.', '.'), ('/', '/'), ('-', '/')]

日付がほしかったのに、区切り文字だけが返ってきました。マッチ 全体 が必要なら、丸かっこを (?:...) に変えるか、finditerでMatchを受け取って group() を使います。

re.findall(r"\d{4}(?:-|\.|/)\d\d(?:-|\.|/)\d\d", orders)
# ['2026-10-20', '2026.10.20', '2026/10/19', '2026-10/20']
[m.group() for m in re.finditer(r"\d{4}(-|\.|/)\d\d(-|\.|/)\d\d", orders)]
# ['2026-10-20', '2026.10.20', '2026/10/19', '2026-10/20']

ドットのひとこと

findallの結果がやけに短かったり、タプルが出てきたりしたら、まずパターンの丸かっこを数えてみてください。くくるためだけのかっこが、こっそりグループになっていることがよくあるんです。3限目で (ha)+ をfindallで回したら 'ha' しか出なかったのも、これが原因ですよ。

証拠分析室

re.search(r"", text)

戻り値

findallの結果が区切り文字のタプルになっています。2つのかっこを (?: に変えるか、finditerを選んで、日付全体が出るようにしてみましょう。

5-6. マッチしなかったグループ、くり返されたグループ (6分)

グループがマッチに参加しなかった場合、その位置は None になります。? の付いたグループが0回マッチしたときや、| のもう一方が選ばれたときです。

m = re.fullmatch(r"(\d{4})-(\d\d)(-(\d\d))?", "2026-10")
print(m.groups())     # ('2026', '10', None, None)
print(m.group(3))     # None
print(re.search(r"(a)|(b)", "b").groups())   # (None, 'b')

グループに量指定子を付けて何度もマッチさせると、グループには 最後の1回 だけが残ります。

m = re.fullmatch(r"(\d)+", "2026")
print(m.group(0), m.group(1))   # 2026 6

re.findall(r"(\d{1,3}\.){3}\d{1,3}", "a=203.0.113.66 b=198.51.100.7")
# ['113.', '100.']
re.findall(r"(?:\d{1,3}\.){3}\d{1,3}", "a=203.0.113.66 b=198.51.100.7")
# ['203.0.113.66', '198.51.100.7']

(\d)+ は4回マッチしましたが、グループ1には最後の 6 だけが残りました。IPアドレスも、グループには3回目のくり返しである 113. だけが残り、findallはそのグループを返しました。5-1のIPパターンをfindallで使うなら、(?:...) が正解です。

事件解決 (10分)

3種類の区切り -、.、/ は [-./] で、区切りのない 20261019 は ? で受け止めます。年・月・日はそれぞれキャプチャします。

DATE = r"(\d{4})[-./]?(\d\d)[-./]?(\d\d)"

for m in re.finditer(DATE, orders):
    y, mo, d = m.groups()
    print(f"{y}년 {int(mo)}월 {int(d)}일")
# 2026년 10월 20일
# 2026년 10월 20일
# 2026년 10월 19일
# 2026년 10월 19일
# 2026년 10월 20일

韓国語の 년、월、일 は、それぞれ「年」「月」「日」のことです。注文番号もいっしょに取り出したいなら、グループをもうひとつ置きます。グループが4つなので、findallは4つの要素を持つタプルを返します。

re.findall(r"#(\d+) " + DATE, orders)
# [('1001', '2026', '10', '20'), ('1002', '2026', '10', '20'), ('1003', '2026', '10', '19'), ('1004', '2026', '10', '19'), ('1005', '2026', '10', '20')]

証拠分析室

re.search(r"", text)

戻り値

ハイフンしか受け付けない日付パターンです。区切りの位置を [-./]? に変えて、4種類の形をすべて受け入れてみましょう。

事件解決報告書 — 注文5件の日付を年・月・日に分けて整理した。ただし#1005は区切り文字が混ざったまま入力されており、スタッフによる確認が必要。

整理された表を受け取ったチョン社長が、最後の行を指さします。「#1005は 2026-10/20 なんですけど。これもそのまま通っちゃってますよね?」

re.fullmatch(DATE, "2026-10/20")
# <re.Match object; span=(0, 10), match='2026-10/20'>

そのとおりです。2つの [-./]? は、お互いのことを知りません。前で - を使ったら後ろでも - を使え、と伝える方法が今のパターンにはないのです。所長が手帳に1行書きつけます。「前で捕まえたものを後ろでもう一度呼ぶ方法がある。それは10限目で」今日はこの1件を人の目で確認して終わりにします。

練習問題

  1. re.findall(r"ship=(done|wait)", orders) の結果を予想してみましょう。(?:done|wait) に変えると、どう変わるでしょうか。
  2. re.search(r"(\d{4})-(\d\d)(-(\d\d))?", "2026-10 発送") で、groups() は何になるでしょうか。
  3. 時刻 03:11:02 から時・分・秒をグループで取り出すパターンを書き、m.group(1, 3) の結果を確かめてみましょう。

所長の手帳

  • ( ) はくくってひとかたまりにし、マッチした文字を覚えておく。(ab)+ のように量指定子を付ける。

  • | は優先順位がいちばん低い。^a|b$ は「^a または b$」。選択肢は左から試す。

  • グループ番号は開きかっこの順。group(0) は全体、groups() はタプル、m[1] は group(1)。

  • (?:...) はくくるだけで、番号を受け取らない。

  • findall:グループ0個なら全体、1個ならそのグループ、2個以上ならタプル。全体がほしければ (?:...) か finditer。

  • マッチしなかったグループは None、(\d)+ のようにくり返したグループは最後の1回だけが残る。

← 前へ

4限目 · 偽の会員登録:アンカーと検証

次へ →

6限目 · 午前3時のアクセス:名前付きグループとMatchオブジェクト

新しい授業・講座のお知らせを受け取る

この講座に授業が追加されたり、続きの講座が公開されたりしたらメールでお知らせします。

収集項目:メールアドレス・目的:この講座の新しい授業・公開のお知らせ・保有期間:解除のご依頼まで。他の目的には使わず、第三者にも提供しません。お知らせメールに返信いただければすぐに削除します。

質問と回答

この回について気になることを書き込んでください。すべての言語のページの質問がここに集まります。

リンクや書式なしで、そのままの文字で表示されます。

R

ReflexKR

触って学ぶPythonとReflex。すべての例はReflex 0.10.0で実際に動かして確認しています。

ReflexKRはバナー広告なし・無料で運営しています。役に立ったら、コーヒー一杯で応援してください。 ☕ コーヒーで応援する

アフィリエイトと支援について
한국어English日本語简体中文EspañolDeutsch

© 2026 ReflexKR · Since 2026 · CC BY-NC 4.0

Built with Reflex