Hatena::ブログ(Diary)

名もないテクノ手 このページをアンテナに追加 RSSフィード Twitter

EPUB版『InDesign者のための正規表現入門』

InDesignのTips一覧

2009-12-01

[][]【正規表現01】日付の表現

はじめに

正規表現が便利だとわかっていても、思い通りに使いこなすのはなかなか難しいと感じる方も大勢いらっしゃると思います。難しいメタ文字を覚えるのが正規表現ではありません。検索をモデル化する「考え方」がもっとも重要です。簡単な表現からひとつひとつ学んで行けば、少しづつ使えるようにるでしょう。いくつかのメタ文字を暗記してみたけれど「実際に」どう使えばいいのか(正しいのかどうか)迷っている方は参考にしてみてください。

これからたまに(気が向いた時に)「正規表現のワンポイントレッスン」を取り上げていきます。なにかしら具体的な実例を上げて、その解法を考えてみます。よかったら、みなさんも考えてみませんか? 解法を思いついたら、気軽にコメントを付けてみてください。解答は翌日(または数日後)エントリーに追記します。

このブログの読者はDTPユーザーが多いですから、正規表現の検証や図示にはAdobe InDesign CS4 (V6.0) 日本語版 Macintosh版 (旧製品)を主に使います。このInDesign CS4では拡張正規表現が採用されていますから、テキストエディタmiやプログラム言語のRubyなどでも類似点が多いと思います。適宜読み替えていただければ幸いです。


日付の表現

日付の表現を考えてみましょう。たとえばこんな感じの日付です:

1月23日

2月28日

3月1日

4月30日

12月31日

  • 日付の数字は半角のアラビア数字のみとします。
  • 月を表す数字の後に「月」、日にちを表す数字の後に「日」が入ります。

マサヒコ*1「できました! 『\d+月\d+日』ですね。カンタンじゃん!」

せうぞー「うーん、惜しいなあ。間違ってはいないんだけどね」

マサヒコ「そうなの? だって上の例文にはぜんぶマッチするよ」

せうぞー「そう。例文には全部マッチする。じゃあこれはどうだろう」

0月0日

23月456日

マサヒコ「あれっ? めちゃめちゃな日付にもマッチしちゃう。うーん、こまったなあ...」

せうぞー「じゃあ、明日まで考えてみて。お友達と相談してもいいよ」

マサヒコ「はあい。みんな助けてね〜!(他力本願)」








みなさん、いくつものコメントありがとうございます*2。正解だったひとも惜しかった人も、いろいろ考えることで「考え方」を深めていただけたかと思います。もしちょっと恥ずかしい間違いをしてしまって「削除して〜!」と思っている人はお申し出くださいませ。「間違ってしまうのは恥ずかしいことじゃない」と、ぼくは思いますけどね... ;-)


考え方

まず、月の数字から考えてみましょう。もっとも陥りがちな罠は、1から12までをこんな風に表現してしまうことです。

1?[0-9]月

こうしてしまうと、「0月」や「19月」にもマッチしてしまうんですね。これはちょっとゆるい。ユルユルです。

1桁と2桁で分けて考えてみましょう。1桁の月数は「[1-9]月」と表現できます。2桁の月数は「1[012]月」と表現できます。ですからこの2つを選択で繋いでしまえばいいんですね。

([1-9]|1[012])月

日にちも同様に考えられます。1桁の「[1-9]日」、10日〜29日は「[12][0-9]」と表現できます。最後の30日と31日は「3[01]日」と書けばいいですね。選択で繋ぐとこうなります。

([1-9]|[12][0-9]|3[01])日

さて、この月日のふたつの正規表現を合体すれば日付を表現できたことになります。めでたしめでたし。

([1-9]|1[012])月([1-9]|[12][0-9]|3[01])日

マサヒコ「先生、まちがえてるよ。だって、これだと『2月31日』にもマッチしちゃう!」

せうぞー「マサヒコくん、余計なところによく気がつきました。確かに小の月、大の月にちゃんと対応していないですね」


ちょっと厳密に考えるの巻

月ごとの最終日をちゃんと正規表現で考えるとどうなるでしょうか? 28日(または29日)しかない2月、30日までしかない4・6・9・11月、31日まである1・3・5・7・8・10・12月のグループに分けられますね。分かりやすく行をわけて書いてみましょう。

(
2月([1-9]|[12][0-9])日|
([469]|11)月([1-9]|[12][0-9]|30)日|
([13578]|1[02])月([1-9]|[12][0-9]|3[01])日
)

かなり複雑になってきました。1行で書くともれなく混乱しますね。しかも閏年かどうかがわからない限り「正当な」日付であるかどうかを判定できません。

さらにこうして正確さを追求することで、もうひとつの不具合がでてきます。それは「11月31日」です。はい「11月31日」自体のマッチには失敗しますが「1月31日」にマッチしてしまうんですね。miで検索するとこんな感じです。

f:id:seuzo:20091201174551g:image


どこまで正規表現でやるのか問題

途中からマッチしてしまう問題に対しては、こんな感じに書いたらどうでしょう?

(?<![0-9])(2月([1-9]|[12][0-9])日|([469]|11)月([1-9]|[12][0-9]|30)日|([13578]|1[02])月([1-9]|[12][0-9]|3[01])日)

否定後読みが使えない場合は、「(^|[^0-9\r\n])」などとしてグループを別処理しなければなりません。ちょっと複雑すぎます。かつ、閏年問題は依然残ったままです。

マサヒコ「げげーっ! これだから正規表現ってキライだよ!」

せうぞー「そう、みんなの正規表現嫌いは、こんなことが原因なんだよね。あるしきい値を超えると急に複雑になってしまう。どんな用途が正規表現に向いているか(または向いていないか)がわからない」


最初にこの問題を選んだのは、ちょっとイジワルだったかもしれません。でも悪気があったわけじゃないんです。正規表現には限界があるってことを身近な例で知っていただきたかったんです。*3

それに、どんな文字列がマッチするのか(またはマッチしないのか)を探り、深く考えることこそ「正規表現を知る近道です。この日付の例で言うならば「([1-9]|1[012])月([1-9]|[12][0-9]|3[01])日」でざっくりチェックして、あとは目視するとしたら小の月の最終日に注意すればいいとわかります。その妥当性を自動で判定しなければならないとしたら、正規表現のマッチ部分を別プログラムに渡すなどします。

$ ruby -r date -e 'p Date::exist?(2009, 2, 29)'
# => nil 2009年2/29は存在しないのでnilが返った

テキスト処理の世界は罠に満ちています。正規表現はその罠に目印をつけてくれる便利な道具です。これから(一緒に|楽しく)勉強していきましょう。

*1正規表現を覚えて間もない生徒。(先生の期待通りに)予定調和な失敗をしてくれる。名字はもちろん「近藤」!

*2:Twitterなどでつぶやいていただいた方もいらっしゃいますので、次回からはハッシュタグを考えた方がいいかもしれません。

*3:たとえば詳説 正規表現には、メールヘッダに記されているメールアドレスが正しいかどうかを判定する複雑な正規表現の例が挙げられている。

モズオモズオ 2009/12/01 16:16 はじめまして.
いつも大変参考になる情報を有難うございます!

問題の正規表現ですが,
(1[0-2]|[1-9])月(1[0-9]|2[0-9]|3[0-1]|[1-9])日

でどうでしょうか!?

YoushiYoushi 2009/12/01 20:48 ([13578]|1[02])月([1-9]|[12][0-9]|3[01])日|([469]|11)月([1-9]|[12][0-9]|30)日|2月([1-9]|[12][0-9])日
もうちょっと正規化できるかもしれませんけど。

YoushiYoushi 2009/12/01 21:02 あー。これだと13月とかにもヒットしちゃいますね。
(?<![0-9])(([13578]|1[02])月([1-9]|[12][0-9]|3[01])日|([469]|11)月([1-9]|[12][0-9]|30)日|2月([1-9]|[12][0-9])日)
こんな感じで。

seuzoseuzo 2009/12/02 10:18 >>モズオさん
コメントありがとうございます。
おそらく見やすさのために「1[0-9]|2[0-9]」と書いておられますが
「[12][0-9]」と書いてもかまいません。

>>Youshiさん
>これだと13月とかにもヒットしちゃいますね。

そうです。こういう風に思わぬマッチに気がつくか、気がつかない(でそのまま置換してしまうか)で、大きな差があります。これに気がついていただいたことが、今回の成果でした。
ありがとうございます。

seuzoseuzo 2009/12/02 10:20 ブログなどで記事を起こしてくださった方、Twitterでつぶやいてくださった方、
ありがとうございました。
次回は(あるかどうかわかりませんが)、もっと素直な出題にしたいと思います^^

なかとじなかとじ 2009/12/02 16:52 正規表現講座。うれしいです。
やろうやろうと思いつつ、なかなかしっくりくる情報に出会えなかったので先送りにしていました。覚えるとプログラミングなんかも手数が増えるんですよね。

seuzoseuzo 2009/12/02 17:34 正規表現が使えると、InDesignだけじゃなく原稿のテキスト整理もとても楽になります。
メタ文字の「次」をテーマにしようと思っていますけれど、いまひとつネタ不足^^

seuzoseuzo 2009/12/03 01:32 こんな別解もあります。

(?<![0-9])
(
([1-9]|1[012])月([1-9]|[12][0-9])日|
([13-9]|1[012])月30日|
([13578]|1[02])月31日
)

すべての月は29日まである。
2月以外は30日まである。
大の月だか31日まである。

RRRRRR 2009/12/03 09:01 おおっ、なるほど。<別解
正規表現は、発想のバリエーションをどれだけ持ち合わせているか、が大きいと思っています。
だからいろんな人の解答(意見)が見られるとよいですよね。
という自分も上げていなかったので、次回からはあげるようにします。

seuzoseuzo 2009/12/03 10:01 >正規表現は、発想のバリエーションをどれだけ持ち合わせているか、が大きいと思っています。

正規表現に限らず、仕事の別解をたくさん持つ人は強いです。
強い人になりたいです。

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証

リンク元