roy > naoya > 基礎プログラミングI·情報検索 > (7)正規表現[1]
電話帳で「ミニマムバリュ」というお店の電話番号を探すことを考える。電話帳では50音順に並んでいるので「ミ」の場所をみれば書いてあることがわかる。では「美作太郎商店」や「美篶形成外科」の電話番号はどのように調べればよいだろうか? 前者は「みさく」「びさく」のどちらだかわからないし(正解は「みまさか」)、後者については全く読めない(正解は「みすず」)。この様な場合は、先頭の文字から調べるのではなく、先頭はわからないが後ろが「太郎商店」や「形成外科」となっている店の番号という方法で調べることができれば便利である。
友人が作ったルビーのプログラムを参考に見せてもらうことになった。ログインをしたが、友人は肝心のファイル名を忘れており、どれだかわからない。「フクシ.rb」という名前にしたはずだが「fukushi」「fukusi」「hukushi」「hukusi」のどれだか思い出せないようだ。途中に「kus」が含まれるファイルという検索ができれば便利である。
電話で名乗られたときも同じようなことが考えられる。携帯電話から電話をしており「...トウです」と名乗っているが、電波状況が悪くうまく聞き取れない。おそらく「カトウ」か「サトウ」なのだが判断ができない。こんな場合は「(カまたはサ)トウ」という名字の人という探し方ができれば便利である。「サトウ」と「サイトウ」の聞き分けが難しい場合には「サ(イ)トウ」という名字の人という探し方が必要になるかもしれない。
何かを検索するときに、そのデータに含まれる文字列の一部やパターンを利用することがある。このパターンを表現するために使用するのが正規表現である。
正規表現とは
「FUKUSHI」と「FUKUSI」どちらかを探したい場合、正規表現では次のように検索パターンを書く。
FUKUSH?I
?は直前のHはあってもなくてもよいという指定になる。これで「FUKUSHI」と「FUKUSI」のどちらでも該当する。?以外にも[]を使って表現することもある。
スガ[ハワ]ラ
この場合「スガハラ」でも「スガワラ」でも該当する。[]の中に何文字か入れると、これらのうちのいずれかが含まれればよいという指定になる。
Rubyでは正規表現のパターンであることを指定するために//でくくって指定するので、正確には下記の通りとなる。
/FUJISH?IMA/
/スガ[ハワ]ラ/
正規表現のパターンを指定する際に使用する?や[]などの文字をメタ文字と呼ぶ。
指定したファイルから該当する文字列を含む行を検索するプログラムを作り、実際に検索してみる。
まず、下記の架空の名簿をmeibo.txtという名前をつけて保存する(プログラムを作成する時と同じようにemacsに貼り付けて保存)。
丘本秋子 オカモトアキコ 酒田市 OKAMOTO Akiko 高梁缶 タカハシカン 真室川町 TAKAHASHI Kan 仁科牧子 ニシナマキコ 最上町 NISHINA Makiko 藤嶌楓 フジシマカエデ 庄内町 HUJISHIMA Kaede 町村利郎 マチムラトシロウ 鶴岡市 MACHIMURA Toshiro 市川週一 イチカワシュウイチ 遊佐町 ICHIKAWA Shuichi 多村数馬 タムラカズマ 西川町 TAMURA Kazuma 砂糖真琴 サトウマコト 山形市 SATO Makoto 藤嶋丸子 フジシママルコ 東根市 FUJISIMA Maruko 嶋田多香子 シマダタカコ 天童市 SHIMADA Takako 齋藤由 サイトウユウ 大江町 SAITO Yu 高橋麹 タカハシコウジ 村山市 Takahasi Koji 渡部すず ワタベスズ 米沢市 WATABE Suzu 渡辺勇次 ワタナベユウジ 小国町 WATANABE Yuji 藤島真希 フジシママキ 寒河江市 FUJISHIMA Maki
次に、以下のプログラムをkensaku.rbという名前をつけて保存する。
#!/usr/koeki/bin/ruby while line=gets if /sai?to/i =~ line print line end end
保存をしたら実行してみる。実行する際は、いつもとは若干方法が異なり、検索対象となるファイル名を指定する必要がある。以下では、ruby kensaku.rbの右にmeibo.txtと書いてある。検索対象としてmeibo.txtというファイルを指定していることになる。
irsv{naoya}%ruby kensaku.rb meibo.txt[Return] 砂糖真琴 サトウ マコト さいたま市 SATO Makoto 齋藤由 サイトウ ユウ 下関 SAITO Yu
続いてプログラムの各行について解説する。
getsはキーボードから入力された値を文字列として取得するメソッドとして利用してきた。しかし、今回のプログラムを実行しても入力は求められなかったはずである。getsメソッドはキーボードからの入力を受け取る以外に、ファイルの中に書かれている内容を読み込むこともできる。プログラムを実行する際にファイルを指定しなければキーボードからの入力を受け取り、ファイルを指定しているとファイルの中味を読み込むことができる。今回は実行時に、meibo.txtと指定しているのでこのファイルの中味を読み込むことになる。
getsメソッドをファイルの指定なしで使用した場合、キーボードから入力した値を読み込む。入力をする際には最後に必ず[Return]を押す。[Return]はUNIXのコンピュータの中では\nという改行文字として受け取られている。ファイルから読み込みを行う場合も、同様に\nを区切りとしてとらえる。1回でファイルの内容を最後まで読み込んでしまうのではなく、改行文字にたどり着くまでを1つの値として読み込み、ここでは変数lineに代入する。
meibo.txtは行末で改行が行われているため、結果的にgetsメソッドは1行読み込むことになる。なお、while line=getsはデータを読みこめる間は以下の処理を行いなさいという意味になる。meibo.txtは15行あるので、15回繰り返され、16回目になると読み込むデータがなくなるのでwhileの繰り返しから抜ける。
この行で読み込んだ行が検索パターン(正規表現)にマッチしているかどうか判定を行う。正規表現にマッチしているかどうかは
/正規表現/ =~ 文字列
と表記する。=~は「正規表現にマッチする」という比較演算子である。反対に「正規表現にマッチしない」は!~とあらわす。
if /sai?to/i = ~lineでは、検索パターンは「sai?to」である。これは「saito」もしくは「sato」をあらわす。//の後ろにあるiは大文字と小文字を区別しないという意味を付加するもので、必要に応じてつけたり、つけなかったりする。iをつけない場合は「sato」はマッチするが、「SATO」「Sato」「sAtO」などはマッチしなくなる。
lineという変数には、ファイルから読み込んだ行が入っている。上記の条件に合致する場合に、その行が出力される。
ifに対応するend。
whileに対応するend。
正規表現を用いたプログラムに関するまとめ
kensaku.rbプログラムの/sai?to/の部分を様々に変更して結果がどうなるか試してみる。これにあたり、まず/sai?to/の中を日本語にするとうまく実行できないことが確認しよう。
日本語にマッチさせるためには/の後ろにiの代わりにeまたはsをつける必要がある。
一通り試したら、下記の問題について考えてみる。
制限時間は10分。完成しない場合は、途中まででも構わないので実行し、結果をメールで送ること。出席点は2点。提出要領は下記の通り。
Tips:emacsでの日本語入力のオンオフはCtrl-oです
Tips:Mewによるメールの送り方はMewコマンドを参照
正規表現によるパターンが英数字のみで書かれている場合、単純に文字列の中にその文字が含まれているかどうかでマッチする、マッチしないを判断する。以下にサンプルを示す。正規表現にマッチする文字列は黄色の網掛けで示し、マッチしている部分を赤色で示す。
正規表現:/ABC/(意味:ABCを含む文字列)
#=> ABC、 ABCDEF、 123ABC、 A1B2C3、AB、abc
/ABC/というパターンは、ABCを含む文字列であれば全てマッチする。しかし場合によってはABCから始まるのののみを検索したり、最後がABCで終わっているものを検索することもある。「abcdef」はマッチするが「123abc」はマッチしないようにするためには、先頭がabcで始まっているものとすればよい。「123abc」にマッチし、「abcdef」にマッチしないようにするには最後がabcで終わっているものという指定をすればよい。
このためには、「^」や「$」といった特殊な文字を使用する。「^」は行頭マッチング、「$」は行末マッチングをあらわす。
正規表現:/^ABC/(意味:ABCで始まる文字列)
#=> ABC、 ABCDEF、 123ABC
正規表現:/ABC$/(意味:ABCで終わる文字列)
#=> ABC、 ABCDEF、123ABC
イトウもしくはゴトウのように「イかゴ」のどちらかを含むという条件を指定する場合、マッチさせたい文字の集合を[]で囲む。[]内のどれかが該当すればマッチすることになる。
正規表現:/[AB]/(意味:AまたはBを含む文字列)
#=> DNA、 Book、 BAC、 Cat、DOT
正規表現:/[ABC]/(意味:A、B、Cのいずれかを含む文字列)
#=> DNA、 Book、 BAC、 Cat、 DOT
正規表現:/[CBA]/(意味:A、B、Cのいずれかを含む文字列。上と同じ)
#=> DNA、 Book、 BAC、 Cat、 DOT
正規表現:/[012ABC]/(意味:0、1、2、A、B、Cのいずれかを含む文字列)
「大文字のアルファベットが含まれていればなんでもOK」というような正規表現を指定する場合、この書き方では[]の中に26文字書かなければならなくなる。この場合は[]の中を○−○というようにハイフンを使って範囲で示すことができる。
正規表現:/[A-Z]/(意味:アルファベットの大文字を含む文字列)
#=> 028A、 Book、 Cat、 dog、075、6-4
正規表現:/[a-z]/(意味:アルファベットの小文字を含む文字列)
#=>028A、 Book、 Cat、 dog、 075、6-4
正規表現:/[0-9]/(意味:数字を含む文字列)
#=> 028A、 Book、Cat、dog、 075、 6-4
正規表現:/[A-Za-z]/(意味:アルファベットを含む文字列)
#=> 028A、 Book、 Cat、 dog、 075、6-4
正規表現:/[A-Za-z_]/(意味:アルファベットとアンダーバーを含む文字列)
正規表現:/[A-Za-z_-]/(意味:アルファベットとアンダーバーとハイフンを含む文字列)
正規表現:/[ぁ-ん]/(意味:ひらがなを含む文字列「ぁ」は小文字)
正規表現:/[亜-腕]/(意味:漢字を含む文字列。ただし第一水準のみ)
ハイフンは範囲を示すために使用するので、ハイフン自体を検索対象の文字列としたい場合は、最初か最後に書かなければならない。
なお行頭マッチングで使用した^は[]内で使用するとそこで指定されたもの以外の文字という意味になる。例えば、[^ABC]はA、B、C以外の文字、[^a-z]はアルファベットの小文字以外の文字ということになる。
正規表現は組み合わせて使用することができる。ここまでに出てきたものを組み合わせると以下のようなものを作ることができる。
正規表現:/a[ABC]c/(意味:aとcの間にA、B、Cのいずれかがある文字列)
#=> aBc、 1aBcDe、 abc
正規表現:/a[^A-C]c/(意味:aとcの間がA、B、C以外の文字である文字列)
#=> aBcabc、 a0c、 malcolm、 aCc
正規表現:/[ABC][AB]/(意味:A、B、Cいずれかの次にAまたはBが続く文字列)
#=> AB、 CA、 AA、 CCCCA、 xCBx、 CC、 CxAx、 C
正規表現:/[0-9][A-Z]/(意味:数字の次に大文字のアルファベットが続く文字列)
#=> 0A、 000AAA、254XBJ
正規表現:/[^A-Z][A-Z]/(意味:アルファベットの大文字以外の文字の次にアルファベットの大文字が続く文字列)
#=> aA2B3C、 NH068A
正規表現:/[^0-9][^A-Z]/(意味:数字以外の文字の次にアルファベットの大文字以外の文字が続く文字列)
#=> 1A2B
.(ピリオド):任意の1文字にマッチする。ピリオド1つなら1文字の文字列、3つなら3文字の文字列をあらわす。
正規表現:/A.C/(意味:AとCの間に何か一文字ある文字列)
#=> ABC、 012A3C456、 AA、 AC、 ABBC、 abc
正規表現:/aaa.../(意味:aaaの後に何か三文字続く文字列)
#=> 00aaabcde、 aaabb
\s(バックスラッシュスモールエス):空白文字をあらわす。空白、タブ、改行文字とマッチする。
正規表現:/ABC\sDEF/(意味:ABCとDEFの間に空白が一つある文字列)
#=> ABC DEF、 ABC\tDEFGH、 123ABC\nDEFG、 ABCDEF
\S(バックスラッシュラージエス):空白以外をあらわす。
正規表現:/ABC\SDEF/(意味:ABCとDEFの間に空白以外の文字が一つある文字列)
#=> ABC3DEF4GHI、 012-ABC-DEF、 ABCrubyDEF、 ABCDEF
\d(バックスラッシュスモールディー):0から9までの数字とマッチする。
正規表現:/\d\d\d-\d\d\d\d/(意味:ハイフンの手前に数字が3桁、後ろに4桁あるもの)
#=> 012-3456、 01234-012345、 ABC-DEFG、 012-21
\D(バックスラッシュラージディー):0から9までの数字以外とマッチする。
正規表現:/\D\d\d\d\d\d\d\D/(意味:数字以外の文字の間に数字が6桁並んでいるもの)
#=> c106999a、 c1068887
\w(バックスラッシュスモールダブリュー):英数字と_(アンダーバー)にマッチする。
正規表現:/\w\w\w/(意味:英数字が3つ連続で続いている文字列)
#=> ABC、 abc、 012、 AB C, AB\nC
\W(バックスラッシュラージダブリュー):英数字と_(アンダーバー)以外にマッチする。
正規表現:/A\WB/(意味:AとBの間が英数字以外の文字列)
#=> A C、 A-C、 ABC、A9C
\A(バックスラッシュラージエー):文字列の先頭にマッチする。^との違いは、^は画面上に出力されている状態で先頭にあればマッチするが、\Aはひとかたまりの文字列の先頭であるかどうかを見る。具体的には、123\nABCは文字列の途中に改行文字が入っているが、/^ABC/はマッチするのに対し、/\AABC/はマッチしない。
正規表現:/\AABC/(意味:先頭がABCである文字列)
#=> ABC、 ABCDEF、 012ABC、 012\nABC
\Z(バックスラッシュラージゼット):文字列の末尾にマッチする。$との違いは、$が画面上に出力されている状態で末尾にあればマッチするが、\Zはひとかたまりの文字列の末尾であるかどうかを見る。具体的には123\nABCは文字列の途中に改行文字が入っているが、/123$/はマッチするのに対し、/123\Z/はマッチしない。
正規表現:/ABC\Z/(意味:末尾がABCである文字列)
#=> ABC、 012ABC、 ABCDEF、 012\nABC、 ABC\nDEF
*:直前の文字の0回以上の繰り返しにマッチする。
正規表現:/A*/(意味:Aが何個あってもいいし、なくても良い)
#=> A、 AAAAA、 1、 BBB
正規表現:/A*C/(意味:Cの前にAが何個あってもいいし、なくても良い)
#=> AAAC、 BC、 AAAB
正規表現:/AAA*C/(意味:Cの前にAが2個以上ある)
#=> 012\nAAC、 012AAAAAAAAC、 AC
正規表現:/A.*C/(意味:AとCの間に何かが何個あってもいいし、なくても良い)
#=> AB012C、 AB CD、 ACDE
+:直前の文字の1回以上の繰り返しにマッチする。
正規表現:/A+/(意味:Aが1個以上連続している)
#=> A、 AAAAA、 1、 BBB
正規表現:/A+C/(意味:Cの前にAが1個以上ある)
#=> AAAC、 BAC、 BC、 AAAB
正規表現:/AAA+C/(意味:Cの前にAが3個以上ある)
#=> AAAC、 HAAAAAAC、 AAC、 AC
正規表現:/A.+C/(意味:AとCの間に何かが1個以上ある)
#=> AB012C、 AB CD、 ACDE
?:直前の文字の0回または1回の繰り返しにマッチする。
正規表現:/^A?/(意味:先頭がAでもAでなくても良い)
#=> B、 A、 AAAA book
正規表現:/A?C/(意味:Cの前にAがあってもなくても良い)
#=> AC、 AAAC、 BC、 C
正規表現:/AAA?C/(意味:Cの前にAが2個以上ある)
#=> AAAAAC、 AAC、 AC
正規表現:/A.?C/(意味:AとCの間に何かがない、もしくは1文字ある)
#=> ACDE、 ABCDE、 AB012C、 AB CD
():*+?と組み合わせて使用することで,1文字単位での繰り返しではなく、複数の文字列の繰り返しにマッチする。
正規表現:/^(ABC)+/(意味:ABCが1回以上繰り返されている)
#=> ABC、 ABCABC、 ABCABCABC、 BCA
正規表現:/HOTO(TO)?GISU/(意味:TOが0回もしくは1回)
#=> HOTOTOGISU、 HOTOGISU、 HOTOTOTOGISU
|:いくつかの候補の中からどれか1つに当てはまるものにマッチする。
正規表現:/(sakura|kashiwa)mochi/(意味:mochiの前にsakuraもしくはkasiwaがある)
#=> sakuramochi、 kasiwamochi、 awabimochi、 yakimochi
メタ文字は組み合わせて使用することができる。幾つか例を示す。
自分で自由にリストを作り、そのリストから特定の正規表現のパターンにマッチする行を取り出してみる。いろいろなパターンについて試し、その中でも複雑な5つのパターンについて実行した結果をレポートする。具体的には下記の流れにしたがうこと。
Tips:emacsでの日本語入力のオンオフはCtrl-oです
Tips:ktermでのプログラムの実行結果をメールに貼り付けるには、コピーしたい箇所をマウスで選択し、emacs(Mew)上でマウスの真ん中ボタンをクリックする
Tips:Mewによるメールの送り方はMewコマンドを参照