正規表現はいいぞ。 -プログラマーに捧げる正規表現のススメ-
元エスイー、現在は心を折って無職のひきこもりとなってしまった私だが、そうはいってもなんとなく「プログラム開発」やってみたいな、と思うときがある。
そうして出来上がったのが以下の「天狗ダイス」だったりする。
これまでの職務経験でWEBサイトだったり、社内ツールを作ったりとしてきた私だが、存外『正規表現』を利用することが多かった。
「実は意外と正規表現を使いこなせると、プログラマーとしても潰しが効くんじゃない?」ということをそれっぽく語ってみたい。
正規表現とは?
正規表現(せいきひょうげん、英: regular expression)とは、文字列の集合を一つの文字列で表現する方法の一つである。
(Wikipediaより引用)
…私には「そうだね」となっても、触れたことの無い人にはなんのこっちゃ以外の何物でもない解釈である。
具体例を出して、もう少しだけ分かりやすくしよう。(ただしとことん解説するとキリがないので、気になった人はやっぱりググって勉強してね)
数学の『集合』を知っているとより直感的に理解しやすいと思う。
例えば、ひらがなで「にんげん」「いんげん」「しんげん」という3つの文字列を用意しよう。これを上部にある「文字列の集合」という観点から考えてみたい。
この3つの文字列は末尾の「んげん」という部分が一緒だ。
すると、例えば数学でAB+AC=A(B+C)と括れるように、文字列も一緒に括れるのではないか?
3つの文字列をまとめてひとくくりにしてみた:
にんげん or いんげん or しんげん=(に or い or し)んげん
「(に or い or し)んげん」という文字列は、「にんげん」「いんげん」「しんげん」の3つをいずれも表現できているわけだ。これが「文字列の集合を一つの文字列で表現する」ということである。
もちろん、『正規表現』にはきちんと上記の「or」に対応する表記法なども全部決まっていて、それに則った形で表現してあげることで、世間一般の『正規表現』の文字列を作成することができる。
正規表現ルールに則った場合:
にんげん or いんげん or しんげん= [にいし]んげん
正規表現がわかると何が良いの?
では正規表現が分かると何が良いのだろうか。
なお、今回の「文字列」については改行を一切含まないとする。理由は注意点にて。
また、プログラム部分もあくまで例示であり、実際には各言語における正規表現の書き方には差異がある。リファレンスを参照すること。
・法則性はあるが、全く合同ではない文字列が複数存在するとき、それらをまとめて一括で処理できる。
たとえば上記の「んげん」が含まれている程度の法則であれば、
str = 'しんげん';
if (strが「んげん」を含む) {
(何か処理する);
}
くらいのアルゴリズムでいくらでもできる。
しかし、たとえば「HTMLの<a>タグで囲まれている」という場合はどうだろう?
以下で足りるだろうか?
str = '<a>テスト</a>';
if (strが「<a>」を含む) {
(何か処理する);
}
実務上、<a>タグはそのまま終わることはまずない。
href='example.com'みたいに振らなきゃハイパーリンクとして機能しないのだから。
更に、例えばtarget や idなどの属性が振られていることもあるだろう。そうすると、「<a target='_blank' id='test'>テスト<a>」みたいな文字列は上のアルゴリズムではif文をすり抜けてしまう。つまり、これでは全く足りないのだ。
じゃぁ…targetが含まれていたらという分岐を作る?同じようにidも…name属性、もしかしたらstyleも書かれているかもしれない…これらが全部一緒に書かれているか、1つだけ書かれているかもわからないのに…?
一体いくつの条件分岐を作れば、完璧に抜け漏れなく、「<a>タグで囲まれている」というのを満たせるだろうか。これだけでも途方もない労苦が待ち構えていることがわかっていただけるだろう。
一方、正規表現ができるとこれで完成だ。
// /~/はこれが正規表現だよという意味。
// matchは「文字列strが正規表現とマッチするかの判定」
if (/<a.*?>.*?<\/a>/.match(str)) {
(何か処理する);
}
if文1行で、
「<a>テスト</a>」
「<a href='hoge' target='_blank'>あああ</a>」
「<a id='test' class='bold' name='exam'>やっほー!</a>」
みたいなものでも全部反応するのだ。
・マッチしたところを保存して、後の処理に引き渡せる
「<a>タグに囲まれている」ことが保持できたら、例えば「<a>タグ内にある文字列に"test"という文字列を末尾に付与しよう」といったこともやってみよう。
正規表現がない場合、「どこからどこまでが<a>タグ内のテキストなのか」を1文字ずつ検索して探さなければならない。
文字列を1文字ずつ分解して配列にして、for文で回しながら「>」が出たらフラグを立てて、そのあとに「<」が来たらそれでOK?
いや、これでは「<a>あああ<strong>強調!</strong>いいい</a>」とかいう文字列を渡されたら、「あああ」しか抜き出せてない。バグの発生だ!
とまぁ、またもやクソめんどくさいことが待ち構えているわけだ。
正規表現は、それを検索した時にマッチした部分を保持して使用することもできる。
if (/<a.*?>.*?<\/a>/.match(str)) {
str = str.replace(/(<a.*?>)(.*?)(<\/a>)/, '$1$2test$3');
}
上記のようにすると、
<a>テスト</a> → <a>テストtest</a>
<a target='_blank'>あああ</a> → <a target='_blank'>あああtest</a>
<a>あああ<strong>強調!</strong>いいい</a>
→ <a>あああ<strong>強調!</strong>いいいtest</a>
といったように、中身がどのような文字列であったとしても、それが正規表現と合致する文字列である限り、一律で同じ処理を施すことができるのだ。
実際、最初に上げた天狗ダイスは、この正規表現の積み重ねで論理が出来上がっている。
文字列を一般化して集合とし、それらに対して共通の処理を行う。
それが正規表現の強力な利点である。
正規表現を使いそうなシーン
IT業界にいると、意外と正規表現を使えそうなシーンは多い。
・<a>タグに一律でclass='hoge'を追加してほしい
・CSVの「,」を全部「タブ文字」に換えてほしい(TSVにしたい)
・「ほにゃららな条件」に合致した文字列だけ、その部分を取り除いてデータベースのテーブルに入れてほしい
こういうことを注文された時、どう対処するだろう。
一律でclass='hoge'を追加したい。全部手作業でやる?数が少なければいいが、1000個も<a>タブがあったらヤル気の危機だ。
CSVはおおよそカラム×行数だけ、「,」があるわけだが、これを全部タブ文字に打ち換える?
置換使えばいいじゃんとは言うけど、「タブ」って文字はないぜ?じゃぁ…手でタブキーを押すしかない…?
…などなど、正規表現を知らないと、死んだ目で手作業をすることになることも実際多い。
「もし入出力が文字列で、ある程度法則性が見いだせそうだ」と思ったら、ぜひ正規表現のことを思い出してほしい。
最初は何書いているか全くわからなくても、そのうちに使いこなせるようになると、途端に文字列処理が簡単になってくる。
しかも、昨今のプログラミング言語やテキストエディタであれば、標準で正規表現が使用可能な場合が多い。
言語に関わらず、どんな場面でも文字列処理に対して潰しが効くので、スキルとしても非常に有用である。新人の皆様にはぜひとも『正規表現』にチャレンジしてもらいたい。
正規表現の弱点
もちろん、正規表現にも弱点がある。
一番の弱点は改行された、複数行の文字列を直接扱えない場合が多いということだ。
なお、ここでの「改行」とは<br>タブなどではなく、テキストエディタでの改行のことだ。
<a>テスト
テスト2行目</a>
…一応、上のような書き方をされたHTMLがあったとしても、HTMLはちゃんと動く。
だが、残念ながら正規表現では上記の表記にマッチしない。
基本的に、正規表現は「1行の文字列しか扱えない」のだ。
(ただしこれは各言語やテキストエディタが処理する際の標準の挙動なので、複数行対応されている場合はマッチすることがある。)
この場合、例えば「<a>タグがあって</a>がないにもかかわらず行が終わっている」といったことを判定し、強引に改行文字を消すことで処理することもできる(2行を1行にしてしまう)が、それをしていいのかは現場の判断によるだろう。
複数行にまたがって文字列処理をする場合は、場合によっては正規表現で対応できない可能性もあることは覚えておこう。
◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆
プログラマーたるもの、処理を共通化し、手間を省くことこそ本懐であると私は考えている。文字列処理だってその例外ではない。
正規表現をおぼえて、楽ちんでステキな文字列処理ライフを。
おまけ
wikipediaの正規表現実践ページの説明が基礎を学ぶ分にはとても良い出来だと思うので紹介します。
この記事が気に入ったらサポートをしてみませんか?