Help us understand the problem. What is going on with this article?

ワイの正規表現入門

とあるWeb制作会社にて

ワイ「社長、こないだ頼まれたショッピングサイトの件なんですけど」
ワイ「ご注文フォームのコーディング、完了しましたで!」

社長「おお、ありがとうな」

ワイ「ほな、飲みに行ってきますわ!」

社長「・・・いや待てや(まだ15時やし)」
社長「何やこのフォーム」

ワイ「何ですかいな」
ワイ「デザイン通り、完璧にコーディングできてますやん」

社長「いやバリデーションが全くされとらへんがな」

ワイ「グラデュエーション?
ワイ「何を卒業するんでっか」

社長「バリデーションや」
社長「貴様をこの会社から卒業させたろか

バリデーションとは

社長「バリデーションちゅうのは、不正な値やないかどうか確認することや」
社長「例えば数字を入力してほしいフォームに、文字列を入力して送信されても困るやろ?」
社長「そういうのをちゃんとチェックするのがフォームバリデーションや」

ワイ「あ、ああ・・・そっちのバリデーションでっか」

社長「お前の作ったこのフォームを見てみいや」
社長「数量のところにウンって入力しても、そのまま注文画面に進めてしまうやないか」
社長「お客様に何てモノをお届けするつもりや」

ワイ「(いや、そのお客様はむしろ欲しがってますやん)」

社長「今日は飲みに行ってええから、明日ちゃんとバリデーション機能も実装してくれや」

ワイ「おお・・・ホワイト会社や・・・」
ワイ「お先に失礼します・・・」

翌日

ワイ「なあ、ハスケル子ちゃん」
ワイ「昨日ワイが作ったご注文フォームなんやけど」
ワイ「フォーム・バ・リデーションができてないからダメやって」

ハスケル子「そ、そうですか」
ハスケル子「(フォーム・バ・リデーション・・・?)」
ハスケル子「でもまあ、そりゃそうですよね」
ハスケル子「確か、仕様書にも」

各入力項目にバリデーションを掛けてください

ハスケル子「って書いてありましたよ」

ワイ「うん」
ワイ「よう分からんかったから、背景に薄くグラデーションは掛けておいたんやけどね」
ワイ「やっぱアカンかったわ」

バリデーション開始

ワイ「さあ、バ・リデーションをしていこか」

ワイ「まずは数量のバ・リデーションや」
ワイ「数値以外が入ってたらNGにしたい場合は・・・」
ワイ「確か正規表現いうのを使ってやるんや」

JavaScript
const isValid = /正規表現/.test(userInput);

ワイ「↑こんな感じでtestメソッドを使ってやればisValid真偽値が入ってくるはずや」
ワイ「userInputにはユーザーが入力した文字列が入ってくる想定やで」

ワイ「せやから・・・」

JavaScript
const isValid = /[0-9]+/.test(userInput);

ワイ「↑こう書いてやればええな」
ワイ「[0-9]+、つまり0~9の中のいずれかの文字1個以上ありまっせ、ってことや」
ワイ「+は、直前の文字が1回もしくはそれ以上繰り返されるって意味やからな」
ワイ「よっしゃ完璧や」

ハスケル子「待ってください」

「含む」になっちゃう

ハスケル子「/[0-9]+/という正規表現だと」
ハスケル子「0〜9の繰り返しを『含む』って意味になっちゃうので」
ハスケル子「↓こんな文字列でもOKになっちゃいますよ」

  • 5万
  • ほげ10
  • ワイが1番や!!!

ワイ「Oh...」

ハスケル子「なので正規表現は/^[0-9]+$/の方がいいですね」
ハスケル子「^ていうのは〜から始まるって意味で」
ハスケル子「$ていうのは〜で終わるって意味です」
ハスケル子「今回の例で言うと」

0〜9の繰り返しで始まって、そのまま終わる文字列

ハスケル子「って感じです」
ハスケル子「つまり^$で挟むと部分一致ではなく完全一致になるってことですね」

ワイ「おお、ありがとう」
ワイ「危ない危ない・・・」
ワイ「ウ〇コが10、とかでもOKになってしまうところやったわ・・・」
ワイ「配達員さんも困るで」

ハスケル子「ちなみにこの正規表現だと、1文字目が0でもOK扱いになります」
ハスケル子「なので、001とかもOKになりますね」
ハスケル子「それをNGにしたい場合は」
ハスケル子「/^[1-9][0-9]*$/
ハスケル子「↑こうですね」

  • 最初に1〜9が1つある → ^[1-9]
  • その後ろに0〜9が0個以上ある → [0-9]*
  • そのまま終わる → $

ワイ「そっかそっか」
ワイ「*は、直前の文字の0回以上の繰り返しってことやもんな」
ワイ「なるほどなぁ〜」

繰り返しの回数を指定する

ワイ「次は姓・名を入力するフォームのバリデーションやな」
ワイ「ええと、の入力ルールとしては」
ワイ「3文字以内であること・・・それくらいチェックしておけばええかな」

ハスケル子「いや勅使河原さんとかどうするんですか」
ハスケル子「仕様書ちゃんと読んでください」
ハスケル子「姓は1〜30文字って書いてありますよ」

ワイ「おお、ありがとうやで」
ワイ「30文字以内ってのはどうやって調べればええんやろ・・・」

ハスケル子「ええと」

  • 任意の文字 → .
  • それが1〜30個あること → {1,30}
  • 「含む」ではなく「完全一致」 → ^$で挟む

ハスケル子「って感じだから」

JavaScript
const isValid = /^.{1,30}$/.test(userInput);

ハスケル子「↑こうですね」

ワイ「今回も完全一致なんやね」

ハスケル子「はい」
ハスケル子「だって『含む』だと35文字でもOKになってしまいますからね」

ワイ「なるほどや」

ハスケル子「でも、ここは無理に正規表現を使う必要ないかもですね」

ワイ「ん?どういうこと?」

無理に正規表現で頑張る必要はない

ハスケル子「つまり↓こんな感じで書いてもいいってことです」

JavaScript
const isValid = 0 < userInput.length && userInput.length <= 30;

ハスケル子「単にlengthで済みますからね」
ハスケル子「無理に正規表現で頑張るより」
ハスケル子「JSの便利なメソッドたちを活用していきましょう」

ワイ「なるほどな」

ハスケル子「ちなみにサロゲートペア1のことも考えるなら」
ハスケル子「userInput.lengthより」
ハスケル子「[...userInput].lengthの方がいいですね」

ワイ「デリケートビア・・・」
ワイ「ああ、腐りやすいビールのことやな?」

ハスケル子「サロゲートペアです」
ハスケル子「絵文字や一部の漢字は4バイト文字なので、1文字で2文字分にカウントされちゃう、ってやつです」

ワイ「なるほどな」
ワイ「最近は絵文字を含んだ苗字の人もおるもんな」

ハスケル子「そうですか」

ワイ「(相変わらずツッコミ冷たいな・・・)」

ハスケル子「(腐ったビールでも飲んでればいいのに)」

指定した選択肢の中の文字列のみOKにする

ワイ「次は、年号のバリデーションやな」
ワイ「大正・昭和・平成・令和だけをOKにしたいんや」
ワイ「せやから・・・」

JavaScript
const isValid = /^大正$|^昭和$|^平成$|^令和$/.test(userInput);

ワイ「↑こうやな」
ワイ「記号の|『または』って意味やから」

  • ^大正$
  • または^昭和$
  • または^平成$
  • または^令和$

ワイ「ってことや」

ハスケル子「それでもいいですけど、もっと短く書けますよ」

ワイ「そうなん?」
ワイ「えーと」
ワイ「/^大正|昭和|平成|令和$/とか?」

ハスケル子「惜しいですね」
ハスケル子「それだと」

  • 大正から始まる文字列
  • または昭和を含む文字列
  • または平成を含む文字列
  • または令和で終わる文字列

ハスケル子「上記の全部に当てはまってしまいます」
ハスケル子「つまり」

  • 大正生まれやで!
  • 多分平成かなぁ
  • キラキラの令和

ハスケル子「↑こんな文字列までOKになっちゃいます」

ワイ「ガバガバやん・・・」
ワイ「^$の方が、|より先に評価されてしまうんやな・・・」

ハスケル子「はい」
ハスケル子「なので」

JavaScript
const isValid = /^(大正|昭和|平成|令和)$/.test(userInput);

ハスケル子「↑こうですね」

ワイ「^(大正|昭和|平成|令和)$かぁ」
ワイ「括弧をつけるんやね」

ハスケル子「括弧でグループ化すると」
ハスケル子「その中は先に評価してもらえます」
ハスケル子「なので・・・」

  • 大正または昭和または平成または令和
  • ^$で挟んであるので「含む」ではなく「完全一致」

ハスケル子「ちゃんと↑こういう意味に評価してくれるんです」
ハスケル子「でも、この場合も無理に正規表現にしなくてもいいですね」

JavaScript
const isValid = ["大正", "昭和", "平成", "令和"].includes(userInput);

ハスケル子「↑これでいいわけですからね」

否定先読み

ワイ「さて次は」

  • 無職から始まる文字列であること
  • ただし無職やめ太郎から始まる文字列はNG

ワイ「こういうバ・リデーションや」

ハスケル子「(どんなご注文フォームなんだろう)」
ハスケル子「(やめ太郎さん、出禁にされてるのかな・・・)」

ワイ「こういう否定みたいなのは正規表現でどうやって表すんやろ」

ハスケル子「ええと」

JavaScript
const isValid = /^無職(?!やめ太郎)/.test(userInput);

ハスケル子「↑こうですね」
ハスケル子「(?!やめ太郎)否定先読みっていう書き方です」

  • 無職から始まる → ^無職
  • ただし後ろにやめ太郎が続くのはNG → (?!やめ太郎)

ハスケル子「↑こういう意味です」

否定後読み

ワイ「次は」

  • 太郎を含むこと
  • ただしやめ太郎はNG

ワイ「↑この条件や」

ハスケル子「(これは厳し目な出禁だな・・・)」

JavaScript
const isValid = /(?<!やめ)太郎/.test(userInput);

ワイ「↑こうやな!」

ハスケル子「合ってますけど、その否定後読みES2018で追加されたものなので」
ハスケル子「FirefoxSafariで使えないですね・・・」

ワイ「おお、そうなんか・・・」

ハスケル子「なので」

JavaScript
const isValid = /太郎/.test(userInput) && !/やめ太郎/.test(userInput);

ハスケル子「こんな感じにしておきましょう」

ワイ「おお、2つ組み合わせるのもええね」

ほかにも色々

ワイ「次は」

  • 1st, 2nd, 3rd, 4th
  • 11th, 12th, 13th, 14th
  • 21st, 22nd, 23rd, 24th

ワイ「↑こんなやつだけをOKにしたいんや!」
ワイ「英語の序数ってやつやな」

ハスケル子「ええと」

JavaScript
const isValid = /^(([0-9]*[02-9])?(1st|2nd|3rd)|([0-9]*([04-9]|1[1-3])th))$/.test(userInput) && /^[1-9]/.test(userInput);

ハスケル子「↑こうですね」

ワイ「おお」
ワイ「けっこう長いな」

ハスケル子「はい」
ハスケル子「日本語で説明すると」

  • 1文字以上の数字の後ろにst,nd,rd,thがつく形式

ハスケル子「↑ざっくりこんなルールですよね」
ハスケル子「細かく定義すると・・・」

  • 下1桁1の時は末尾にstがつくべき
  • 下1桁2の時は末尾にndがつくべき
  • 下1桁3の時は末尾にrdがつくべき
  • ただし下2桁11,12,13の時はthがつくべき
  • それ以外の場合は末尾にthがつくべき
  • 1~9から始まるべき(0からは始まらない)

ハスケル子「↑こんなルールですよね」

ワイ「せやな」

ハスケル子「1桁なら割とシンプルで」

JavaScript
const isValid = /^(1st|2nd|3rd|[4-9]th)$/.test(userInput);

ハスケル子「↑こんな感じです」

ワイ「なるほどな」

  • 1stまたは2ndまたは3rdのパターン → 1st|2nd|3rd
  • または4〜9の後ろにthがつくパターン → [4-9]th
  • それを^()$に入れて完全一致にする

ワイ「↑ってことやな」

ハスケル子「はい」
ハスケル子「それが2桁以上になると」

  • ただし下2桁11,12,13の時はthがつくべき
  • 1~9から始まるべき(0からは始まらない)

ハスケル子「↑こんなルールが加わるので」

JavaScript
const isValid = /^(([0-9]*[02-9])?(1st|2nd|3rd)|([0-9]*([04-9]|1[1-3])th))$/.test(userInput);

ハスケル子「↑こんなに長くなります」

ワイ「う〜ん・・・」
ワイ「11,12,13の場合は11st,12nd,13rdやなくて11th,12th,13thになるわけやから・・・」

  • 1st,2nd,3rdの前に1は来ない。つまり0または2〜9が来る。
    → [02-9]
  • 下1桁が0または4〜9の時はthがつく。または下2桁11,12,13の時もthがつく。
    → ([04-9]|1[1-3])th

ワイ「↑ってことかぁ・・・」

ハスケル子「はい」
ハスケル子「でも、これでもまだ・・・」

  • 1~9から始まるべき(0からは始まらない)

ハスケル子「↑このルールが満たせていません」

ワイ「まじか・・・」

ハスケル子「そこも全て正規表現で表そうとすると、更に長くなってしまうので・・・」

JavaScript
 && /^[1-9]/.test(userInput);

ハスケル子「↑この条件を加えることで0始まりをブロックしています」

ワイ「なるほどな」

  • 1〜9から始まらなあかんよ → ^[1-9]

ワイ「↑ってことやな」

ハスケル子「はい」
ハスケル子「今回は割と長めの正規表現で頑張りましたが」
ハスケル子「実際にはもっと分割して&&で繋いだ方がいいですね」

ワイ「なるほどなぁ」
ワイ「実務のコードで長〜い正規表現を書かれても、読まれへんもんな・・・」

正規表現完全理解ワイ

ワイ「とにかく」
ワイ「正規表現、完全に理解したわ」

ハスケル子「これでやめ太郎さんも正規表現いっぱい書けますね!」

ワイ「おう!」
ワイ「理論上は可能や!

何でそんなにできるようになったん?

ワイ「ていうかハスケル子ちゃん」
ワイ「何でそんなに正規表現できるようになったん?」

ハスケル子「このサイトで練習しました」

ワイ「おお」
ワイ「正規表現を試しに書いて」
ワイ「実際にどういった文字列が該当するのかが分かるんやな」
ワイ「ワイもこのサイトで勉強してみるわ・・・!」

やめ太郎の戦いは続く・・・

他にもおすすめサイト


  1. 絵文字や一部の漢字はサロゲートペア文字と呼ばれる4バイト文字なため、1文字で2文字とカウントされてしまいます。 

yumemi
みんなが知ってるあのサービス、実はゆめみが作ってます。スマホアプリ/Webサービスの企画・UX/UI設計、開発運用。Swift, Kotlin, PHP, Vue.js, React.js, Node.js, AWS等エンジニア・クリエイターの会社です。Twitterで情報配信中https://twitter.com/yumemiinc
http://www.yumemi.co.jp
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away