DanLevy.net

クイズ: 正規表現マスター

野生の正規表現を手懐けろ。

正規表現で格闘する準備はできましたか? 🤼‍♂️

RegEx の知識をテストしましょう。基本パターン、量指定子、グループ、そして厄介な先読み・後読みアサーションに関する問題があります。単純な文字列マッチから複雑なパターン検証まで、正しい正規表現を見つけられますか?

何がマッチしますか?

'cat CAT Cat'.match(/cat/g)

このパターンは g を使用していますが、i は使用していません:

  • g はすべての一致を見つけます
  • i がないと、大文字小文字が区別されます

i フラグがない場合、小文字の “cat” のみが一致します。

ユーザー入力や HTML など、ケースが変わり得る場面で特に有用です。

RegExp フラグの詳細はこちら

このコードは何を返しますか?

const words = ['cat', 'hat', 'what', 'bat'];
words.filter(word => word.match(/^[ch]at/))

パターン /^[ch]at/ は次の文字列にマッチします:

  • 先頭 (^) が ‘c’ または ‘h’ のいずれかであること([ch] は1文字の文字クラスを意味します)
  • その後に文字通り ‘at’ が続くこと

したがって、“cat” と “hat” だけがこのパターンにマッチします。filter() メソッドはマッチした要素だけを残します。

MDNで文字クラスの詳細を見る

これは何にマッチしますか?

'<div>Hello</div><div>World</div>'.match(/<div>.*?<\/div>/g)

パターン /<div>.*?<\/div>/g は *? による非貪欲マッチングを使用しています。意味は次の通りです:

  • <div> にマッチ
  • 任意の文字列 (.*) をできるだけ少なく (?) マッチ
  • </div> が見つかるまで続ける
  • g フラグで全ての出現箇所をマッチ

? が無ければ、貪欲な .* は最初の <div> から最後の </div> まで全てをマッチさせ、1つの大きなマッチになります。? を付けると、各ペアを個別にマッチします。

貪欲 vs ラジーマッチングの詳細

これの戻り値は何ですか?

'hello\nworld'.match(/\w+/g)

\w+ パターンは1つ以上の単語文字にマッチします。文字列に改行が含まれていても、\w は以下にマッチします:

  • 文字 (a-z, A-Z)
  • 数字 (0-9)
  • アンダースコア (_)

つまり、改行は単語境界として扱われ、2つのマッチが得られます。.* を使った場合、デフォルトでは改行にマッチしません(そのためには s フラグが必要です)。

メタ文字の詳細はこちら

これは何にマッチしますか?

'$100 and €50'.match(/\d+(?=[\$€])/g)

結果は null です。\d+(?=[\$€]) は数字の直後に通貨記号がある位置を探しますが、入力では通貨記号が数字の前にあります。

通貨記号の後の数字を取り出すには、肯定後読み /(?<=[\$€])\d+/g を使います。先読みは現在位置の後に続く文字を、後読みは現在位置より前の文字を検査します。

アサーションの詳細

何がマッチしますか?

'cat cats category'.match(/\bcat\b/g)

\b は単語境界を表し、次にマッチします:

  • 単語文字と非単語文字の間
  • 文字列の先頭または末尾で、そこに単語文字がある場合

つまり /\bcat\b/ は「cat」が完全な単語として存在する時だけマッチし、他の単語の一部ではマッチしません。

  • ✅ “cat”(スペースで囲まれている)
  • ❌ “cats”(“cat” の後に境界がない)
  • ❌ “category”(“cat” の後に境界がない)

単語境界の詳細はこちら

出力は何ですか?

'banana'.match(/a/g)

g (global) フラグは match() の動作を変えます:

  • g がない場合: キャプチャグループ付きで最初の一致を返す
  • g がある場合: すべての一致文字列の配列を返す

このケースでは、“banana” 中の “a” のすべての出現を見つけます。

注意: すべての一致とキャプチャグループの両方が必要な場合は、matchAll() またはループ内で exec() メソッドを使用してください。

グローバルフラグの詳細はこちら

このパターンにマッチするのはどれですか?

'abc123 def456'.match(/(?<!abc)\d+/g)

否定後読み (?<!abc) は、数字の前に “abc” がないことを保証します:

  • ❌ “123” (前に “abc” がある)
  • ✅ “23” (前に “abc1” がある)
  • ✅ “456” (前に “def” がある)

JavaScript はモダンエンジンで後読みアサーションをサポートしています。この例は固定長の後読みを使用しています: abc は常に3文字です。可変長の後読みはエンジン依存でやや難しいです。

注意: 後読みのサポートは JavaScript では比較的新しい機能です。古いブラウザをサポートする必要がある場合は、ブラウザ互換性 を確認してください。

このコードは何を返しますか?

'2029-12-31'.match(/(\d{4})-(\d{2})-(\d{2})/).slice(1)

このパターンは3つのキャプチャグループを使用します:

  1. (\d{4}) は年をキャプチャ
  2. (\d{2}) は月をキャプチャ
  3. (\d{2}) は日をキャプチャ

g フラグなしの match() は次を返します:

  • インデックス0: 完全一致
  • インデックス1以降: キャプチャグループ

slice(1) はキャプチャグループだけを取得する一般的なテクニックです。

グループとキャプチャの詳細はこちら

これの結果はどれですか?

"123aBc".match(/^\d+(?![a-z])/ig)

i フラグがあるため、(?![a-z]) は直後のASCII英字を大文字小文字に関係なく拒否します。貪欲な数字のマッチはまず 123 を消費しますが、次の a で失敗し、12 までバックトラックします。すると次の文字は 3 なのでアサーションが成功し、結果は ["12"] です。

否定先読みの詳細

返されるのは何ですか?

'a,b,c'.split(/(?<=,)/)

パターン /(?<=,)/ は、カンマの後にマッチする後読みです:

  • a,(カンマの後)
  • b,(カンマの後)
  • c(カンマが後ろにない)

後読みはカンマを消費しないため、分割結果ではカンマが前のセグメントに残ります。

文字列を前にある文字で分割したいが、分割文字を失わずにしたい場合に便利です。

後読みアサーションについて詳しく読む

何がマッチしますか?

'$100'.match(/$\d+/)

特殊文字は文字通りマッチさせるために バックスラッシュでエスケープする必要があります:

  • $ は特殊文字(文字列の終端)です
  • リテラルのドル記号にマッチさせるにはエスケープします: ドル記号の前にバックスラッシュを置きます

エスケープが必要な一般的な文字:

. * + ? ^ $ [ ] \ ( ) { } |

エスケープしないと、多くの特殊文字は正規表現上の意味を持ち、期待通りに動かないことがあります。

特殊文字のエスケープについて詳しく読む

何がマッチしますか?

'$100'.match(/(?<=\$)\d+/)

肯定後読み (?<=\$) は、数字の前にドル記号があることを保証します:

  • (?<=\$): ドル記号の後読み
  • \d+: 1つ以上の数字にマッチ

後読みアサーションは文字を消費せず、前方の文字だけをチェックします。 前の部分を含めずに、直前の文字に基づいて何かをマッチさせたいときに便利です。

後読みアサーションの詳細はこちら

何がマッチしますか?

'<b>bold</b>'.match(/<b>(.*?)<\/b>/).slice(1)

パターンは *? を使った遅延マッチングです:

  • <b>: 開始タグにマッチ
  • (.*?): 任意の文字をキャプチャ(遅延)
  • </b>: 終了タグにマッチ

* の後の ? が遅延にし、できるだけ少ない文字にマッチします。 ? がなければ貪欲になり、可能な限り多くの文字にマッチします。

slice(1) はキャプチャされたグループだけを返します。

貪欲と遅延マッチングの詳細

何がマッチしますか?

'😀 🙂'.match(/\p{Emoji}/gu)

u フラグは以下を有効にします:

  • Unicode プロパティエスケープ(\p{...})
  • サロゲートペアの正しい処理

u がないと、絵文字やその他の Unicode 文字が正しくマッチしない可能性があります。 パターン \p{Emoji} は Unicode の Emoji プロパティを持つ文字にマッチします。この文字列では、2つの絵文字ピクトグラムを指します。

注意: UnicodeプロパティエスケープにはUnicode対応モードが必要です。この例の u フラグ、または新しい v フラグを使用します。

Unicode モードの詳細はこちら

事前に謝ります! 😈
このパターンにマッチするパスワードはどれですか?

/^(?=.*[A-Z])(?=.*[a-z])(?=.*\d)(?=.*[!@#$%^&*]).{8,}$/

本番環境でこんなことは絶対書かないでね! 😅

このパターンは複数のポジティブ・ルックアヘッドを使って以下を強制します:

  • 少なくとも1つの大文字: (?=.*[A-Z])
  • 少なくとも1つの小文字: (?=.*[a-z])
  • 少なくとも1つの数字: (?=.*\d)
  • 少なくとも1つの特殊文字: (?=.*[!@#$%^&*])
  • 最低8文字: .{8,}

ルックアヘッドは文字を消費せずに複数条件をチェックできるので、パスワード検証に最適です。

パスワード検証パターンの詳細はこちら

結果はどうでしたか? 🧐

正規表現は手ごわい相手ですが、使いこなせば(特に最新構文を覚えれば)非常に強力です。練習を続ければ、すぐに正規表現の達人になれます! 🧙‍♂️

正規表現で頭がいっぱいになったら、一息入れませんか?
でも覚えておいてください:スキルの後に休憩です!

もっと挑戦したいなら、my gym へ行ってさらなる課題をクリアしましょう! 💪