はじめに
正規表現は文字列のパターンマッチングにおいて強力なツールですが、正規表現に対する知識不足によって意図せず落とし穴にはまることがあります。
今回は、半角数字で 2 桁以上の数値を含む、範囲の正規表現について考えてみましょう。
まずは簡単な 2 題のクイズを確認します。
Quiz 1
1 ~ 64 までの数値にマッチする正規表現はどれでしょうか?
以下の中から正しいものをすべて選んでください。
1. ^[1-64]$
2. ^[1-9]|[1-5][0-9]|6[0-4]$
3. ^([1-9]|[1-5][0-9]|6[0-4])$
4. ^(\d|[1-6][0-4])$
5. ^([1-9]|[1-6][0-4])$
Quiz 2:
0 ~ 255 までの数値にマッチする正規表現はどれでしょうか?
以下の中から正しいものをすべて選んでください。
1. ^[0-255]$
2. ^0|[1-9][0-9]?|1[0-9][0-9]|2[0-4][0-9]|25[0-5]$
3. ^(0|[1-9][0-9]?|1[0-9]{2}|2[0-4][0-9]|25[0-5])$
4. ^([0-9][0-9]?|1[0-9]{2}|2[0-4][0-9]|25[0-5])$
5. ^(0|[1-9]\d{0,1}|1\d{2}|2[0-4]\d|25[0-5])$
これらの正規表現に関して、期待通りのパターンマッチングを実現する正規表現をすぐに判断できましたか?正規表現の微妙な違いが、マッチング結果に大きな影響を与えます。
それでは、解答とその理由を詳しく見ていきましょう。
※正規表現チェッカーを使用して、手元で正規表現とパターンマッチを確認しながら進めると理解しやすいです。
1 ~ 64 を表す正規表現
1 ~ 64 までの数値を満たす正規表現は、Quiz 1 の選択肢 3 のみです。
Quiz 1
1 ~ 64 までの半角数字にマッチする正規表現はどれでしょうか?
以下の中から正しいものをすべて選んでください。
1. ^[1-64]$ ❌ 不正解
2. ^[1-9]|[1-5][0-9]|6[0-4]$ ❌ 不正解
3. ^([1-9]|[1-5][0-9]|6[0-4])$ ✅ 正解
4. ^(\d|[1-6][0-4])$ ❌ 不正解
5. ^([1-9]|[1-6][0-4])$ ❌ 不正解
これらの選択肢を評価する際に必要な正規表現の知識を下記にまとめます。
▼ 表1 正規表現解説表 1
正規表現 | 意味 |
^ | 行頭一致 |
$ | 行末一致 |
- | 範囲 ※ ただし、[]内で使用された場合 |
[1-9] | 1 ~ 9 までの半角数字に一致 |
[1-36] | 1 ~ 3 までと 6 の半角数字に一致 |
\d | 0 ~ 9 の半角数字に一致 |
| | OR 条件 |
() | グループ化 |
では、この表を踏まえて、それぞれの選択肢について詳しく見ていきましょう。
選択肢1 ^[1-64]$ は ❌ 不正解 です。
この表現は「1 ~ 6 と 4 の半角数字 1 文字」にマッチします。正規表現の文字クラス [...] 内では、ハイフンは範囲を表しますが、[1-64] は「1 ~ 6 と 4 の半角数字 1 文字」という意味になります。[1-64] では、1 ~ 64 までの半角数字という意味にはならないので注意が必要です。
これは実質的に [1234564] と同じで、「1, 2, 3, 4, 5, 6, 4」のいずれか1文字にマッチします。
つまり、「1 ~ 6 までの半角数字 1 文字」のみにマッチするため、13 や 64 にマッチしないため不正解となります。
選択肢2: ^[1-9]|[1-5][0-9]|6[0-4]$ は ❌ 不正解 です。
この表現はグループ化が適切に行われていないため、以下のように解釈されます:
^[1-9]: 行頭が 1 ~ 9 の半角数字 1 文字
[1-5][0-9]: 行内の任意の場所に 10 ~ 59 までの半角数字が存在する
6[0-4]$: 行末が 60 ~ 64 までの半角数字
そのため、例えば「1abc」や「abc12」などの文字列にもマッチしてしまいます。
選択肢3: ^([1-9]|[1-5][0-9]|6[0-4])$ は ✅ 正解 です。
この表現は 1 ~ 64 までの半角数字にマッチします:
[1-9]: 1 ~ 9 までの半角数字1桁の数字
[1-5][0-9]: 10 ~ 59までの2桁の半角数字
6[0-4]: 60 ~ 64までの2桁の半角数字
そして、これらの選択肢を |(OR)で連結し、全体を括弧でグループ化して、文字列の先頭(^)と末尾($)にアンカーを付けています。
つまり、^[1-9]$|^[1-5][0-9]$|^6[0-4]$ と同義ですね。
選択肢4: ^(\d|[1-6][0-4])$ は ❌ 不正解 です。
この表現は以下のようにマッチします:
\d: 任意の 1 桁の半角数字(0 ~ 9)
[1-6][0-4]: 1 ~ 6 までの数字の後に 0 ~ 4 までの半角数字が続く
これでは 0 にマッチしてしまい、また「36」や「59」などの半角数字とマッチしない表現になってしまいます。
選択肢5: ^([1-9]|[1-6][0-4])$ は ❌ 不正解 です。
この表現も選択肢4と同様の問題があります:
[1-9]: 1 ~ 9 までの半角数字1桁の数字
[1-6][0-4]: 1 ~ 6 までの数字の後に 0 ~ 4 までの半角数字が続く
このような条件では、「15」や「28」、「36」や「59」などの半角数字とマッチしない表現になってしまいます。
これらのことから、選択肢 3 が唯一、1 ~ 64 までの半角数字のみにマッチする正規表現であることが確認できました。
0 ~ 255 を表す正規表現
0 ~ 255 までの数値を満たす正規表現は、Quiz 2 の選択肢 3 と 5 のみです。
Quiz 2
0 ~ 255 までの数値にマッチする正規表現はどれでしょうか?
以下の中から正しいものをすべて選んでください。
1. ^[0-255]$ ❌ 不正解
2. ^0|[1-9][0-9]?|1[0-9][0-9]|2[0-4][0-9]|25[0-5]$ ❌ 不正解
3. ^(0|[1-9][0-9]?|1[0-9]{2}|2[0-4][0-9]|25[0-5])$ ✅ 正解
4. ^([0-9][0-9]?|1[0-9]{2}|2[0-4][0-9]|25[0-5])$ ❌ 不正解
5. ^(0|[1-9]\d{0,1}|1\d{2}|2[0-4]\d|25[0-5])$ ✅ 正解
これらの選択肢を評価する際に必要な正規表現の知識を下記にまとめます。
※ 確認しやすくするために、表 1 と重複した内容も表記しています。
▼ 表2 正規表現解説表 2
正規表現 | 意味 |
^ | 行頭一致 |
$ | 行末一致 |
- | 範囲 ※ ただし、[]内で使用された場合 |
[1-9] | 1 ~ 9 までの半角数字に一致 |
[1-36] | 1 ~ 3 までと 6 の半角数字に一致 |
\d | 0 ~ 9 の半角数字に一致 |
| | OR 条件 |
() | グループ化 |
? | 直前の文字を0または1回使用する |
{n} | 直前の文字またはパターンをn回繰り返す |
{m,n} | 直前の文字またはパターンをm回以上、n回以下繰り返す |
では、この表を踏まえて、それぞれの選択肢について詳しく見ていきましょう。
選択肢1: ^[0-255]$ は ❌ 不正解 です。
この表現は「0 ~ 2 と 5 と 5 の半角数字 1 文字」にマッチします。正規表現の文字クラス [...] 内では、ハイフンは範囲を表しますが、[0-255] は「0 ~ 2 と 5 と 5 の半角数字 1 文字」という意味になります。[0-255] では、0 ~ 255 までの半角数字という意味にはならないので注意が必要です。
これは実質的に [01255] と同じで、「0, 1, 2, 5, 5」のいずれか1文字にマッチします。
つまり、「0 ~ 2 までの半角数字 1 文字」のみにマッチするため、149 や 213 にマッチしないため不正解となります。
選択肢2: ^0|[1-9][0-9]?|1[0-9][0-9]|2[0-4][0-9]|25[0-5]$ は ❌ 不正解 です。
この表現はグループ化が適切に行われていないため、以下のように解釈されます:
^0: 行頭が0
[1-9][0-9]?: 行内の任意の場所に 1 ~ 99 までの半角数字が存在する
1[0-9][0-9]: 行内の任意の場所に 100 ~ 199 までの半角数字が存在する
2[0-4][0-9]: 行内の任意の場所に 200 ~ 249 までの半角数字が存在する
25[0-5]$: 行末が250から255までの半角数字
そのため、例えば「0abc」や「abc123」などの文字列にもマッチしてしまいます。
選択肢3: ^(0|[1-9][0-9]?|1[0-9]{2}|2[0-4][0-9]|25[0-5])$ は ✅ 正解 です。
この表現は正しく 0 ~ 255 までの半角数字にマッチします:
0: 数字の 0
[1-9][0-9]?: 1 ~ 99 までの 1 桁または 2 桁の半角数字(先頭が 0 でない)
1[0-9]{2}: 100 ~ 199 までの 3 桁の半角数字(1 の後に任意の半角数字が 2 つ)
2[0-4][0-9]: 200 ~ 249 までの 3 桁の半角数字
25[0-5]: 250 ~ 255 までの 3 桁の半角数字
そして、これらの選択肢を |(OR)で連結し、全体を括弧でグループ化して、文字列の先頭(^)と末尾($)にアンカーを付けています。
つまり、^0$|^[1-9][0-9]?$|^1[0-9]{2}$|^2[0-4][0-9]$|^25[0-5]$と同義ですね。
選択肢4: ^([0-9][0-9]?|1[0-9]{2}|2[0-4][0-9]|25[0-5])$ は ❌ 不正解 です。
この表現は以下のようにマッチします:
[0-9][0-9]?: 0 ~ 99 までの 1 桁または 2 桁の半角数字
1[0-9]{2}: 100 ~ 199 までの 3 桁の半角数字
2[0-4][0-9]: 200 ~ 249 までの 3 桁の半角数字
25[0-5]: 250 ~ 255 までの 3 桁の半角数字
一見正しそうですが、`[0-9][0-9]?`の表現では「01」や「09」のような先頭に 0 がついた 2 桁の半角数字にもマッチしてしまいます。通常、数値として扱う場合は先頭の 0 は無視されるべきです。
選択肢5: ^(0|[1-9]\d{0,1}|1\d{2}|2[0-4]\d|25[0-5])$ は ✅ 正解 です。
この表現も正しく 0 ~ 255 までの数字にマッチします:
0: 数字の0
[1-9]\d{0,1}: 1 ~ 99 までの 1 桁または 2 桁の半角数字(先頭が0でない)
\d は任意の半角数字(0 ~ 9)を表す
{0,1} は直前のパターンが 0 回または 1 回出現することを意味する
1\d{2}: 100 ~ 199 までの 3 桁の半角数字
2[0-4]\d: 200 ~ 249 までの 3 桁の半角数字
25[0-5]: 250 ~ 255 までの 3 桁の半角数字
選択肢3と同様に、これらの選択肢を適切にグループ化し、アンカーを付けています。\d{0,1} は \d? と書くこともできます。
これらのことから、選択肢 3 と 5 が、0 ~ 255 までの半角数字のみにマッチする正規表現であることが確認できました。
まとめ
正規表現は適切に使用する場合に限り便利な機能であり、非常に柔軟で強力ですが、その柔軟性ゆえに思わぬバグを生み出すこともあります。特に複雑なパターンを扱う場合は、十分なテストを行い、エッジケースでも期待通りの動作をするか確認することが重要です。
正規表現を最大限に活用するためには、基本的な構文や特殊文字の意味、そして一般的なパターンについての知識が必要であると感じました。基本を理解し、実践を重ねることで、より効果的に活用できるようになりたいですね。