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

\(._. \)わっしょいって感じの正規表現と向き合う

はじめに

\(._. \)
正規表現がわからなすぎてちょっとイライラしていたところに友人からちょうどこの顔文字が送られてきたからイラッとした。
これも正規表現になりうるのだろうけど、読めない。。

正規表現なんて使用する場面になったらその都度調べればよいだろう。
どこかでそんな心構えでやってきたのですが、いざ使う場面に出くわすと、前提知識が無さすぎて中々しんどい。

とは言っても、一時的に大量に覚えたところですぐに忘れてしまうだろうから基礎の基礎から少しずつ試しに使っていくことにする。

超基本的な正規表現で電話番号を捉える

下記を例とする。電話番号の部分だけを抽出するにはどうするかを考えてみる。

会社名: 株式会社XXX
住所: 東京都港区南麻布1-1-1
本社: 03(2345)6789
携帯番号: 080-1234-5678
フリーダイヤル: 0120(000)000
緊急: 01234-5-6789

共通点や異なる点の確認

① 3つに区切れる
② 2〜5桁、1〜4桁、3〜4桁の構成
③ 区切りはカッコ、またはハイフン
④ 1文字目は0、2文字目は0以外の半角数字

文字数範囲の指定

\d ... 1個の半角数字(0123456789)
{n,m} ... 直前の文字が n 個以上、m 個以下

ゆえに、3〜4桁の数字は\d{3,4}と表現する。

AまたはB

[AB] ... AまたはBのいずれか1文字

ここまでの情報で、
\d{2,5}[-(]\d{1,4}[-)]\d{3,4}
とすれば、共通点や異なる点の①〜③をクリアできる。

text = <<-TEXT
会社名: 株式会社XXX
住所: 東京都港区南麻布1-1-1
本社: 03(2345)6789
携帯番号: 080-1234-5678
フリーダイヤル: 0120(000)000
緊急: 01234-5-6789
TEXT

text.scan /\d{2,5}[-(]\d{1,4}[-)]\d{3,4}/
=> ["03(2345)6789", "080-1234-5678", "0120(000)000", "01234-5-6789"]

例外に注意する

今のままだと以下のような例外も当てはまってしまう。

text = <<-TEXT
会社名: 株式会社XXX
住所: 東京都港区南麻布1-1-1
本社: 03(2345)6789
携帯番号: 080-1234-5678
フリーダイヤル: 0120(000)000
緊急: 01234-5-6789

例外1: 999-9999-999
例外2: 0120-000)000
TEXT

text.scan /\d{2,5}[-(]\d{1,4}[-)]\d{3,4}/
=> ["03(2345)6789", "080-1234-5678", "0120(000)000", "01234-5-6789", "999-9999-999", "0120-000)000"]

まず、1文字目は0、2文字目は0以外の半角数字に対応させるには
0[1-9]\d{0,3}[-(]\d{1,4}[-)]\d{3,4}とすればよい。

※ -(ハイフン)は位置によって意味が変わる。
[1-9] ... 1または2または3、、、、または9
[19-] ... 1または9または-

grepメソッドを使用する

Rubyで使用できるものですが、grepメソッドを用いる。
grep ... 各要素に対して「引数obj === 要素」を試し、その結果が真だった要素を集めて配列にして返す
grepメソッドの引数を正規表現にすると、パターンにマッチする文字列を集められるので、これを利用する。

使用する正規表現を確認
| ... or
\ ... エスケープ
+ ... 直前の1文字の1回以上の繰り返しを表す

()はグループ化を表現してしまうので、\でエスケープする必要がある。
\d+で数字が続くことを表現できる。

0120-000-000 または 0120(000)000を表現するには、
\(\d+\)|-\d+-となる。

text = <<-TEXT
会社名: 株式会社XXX
住所: 東京都港区南麻布1-1-1
本社: 03(2345)6789
携帯番号: 080-1234-5678
フリーダイヤル: 0120(000)000
緊急: 01234-5-6789

例外1: 999-9999-999
例外2: 0120-000)000
TEXT

before = text.scan /0[1-9]\d{0,3}[-(]\d{1,4}[-)]\d{3,4}/
=> ["03(2345)6789", "080-1234-5678", "0120(000)000", "01234-5-6789", "0120-000)000"]
after = before.grep(/\(\d+\)|-\d+-/)
=> ["03(2345)6789", "080-1234-5678", "0120(000)000", "01234-5-6789"]

例外を出しだすときりがないですが、とりあえず正規表現を用いて目的のものを抽出できました。

おわりに

正規表現を勉強する前は、
0[1-9]\d{0,3}[-(]\d{1,4}[-)]\d{3,4},\(\d+\)|-\d+-
これらが暗号の羅列にしか見えなかったものが読めるようになりました。

ついでに\(._. \)こいつについて
. ... 任意の文字
であるから(の_g )とかの 正規表現になるわけですね。

顔文字か正規表現かを見破るアプリゲームとかいいかもしんないですね。

最低限の基礎があれば初見の正規表現に対しても少し調べれば読み解くことができそうなので、最低限を身につけるためにもう少し正規表現を勉強していく必要がありそうです。

参考

初心者歓迎!手と目で覚える正規表現入門・その1「さまざまな形式の電話番号を検索しよう」

Why do not you register as a user and use Qiita more conveniently?
  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
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  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