(7) 5/29の授業内容:正規表現

正規表現とは

電話帳で「ミニマムバリュ」というお店の電話番号を探すことを考える。電話帳では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
砂糖真琴 サトウ マコト さいたま市 SATO Makoto 
齋藤由  サイトウ ユウ 下関    SAITO Yu 

続いてプログラムの各行について解説する。

while line=gets(1行目)

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(2行目)

この行で読み込んだ行が検索パターン(正規表現)にマッチしているかどうか判定を行う。正規表現にマッチしているかどうかは

/正規表現/ =~ 文字列

と表記する。=~「正規表現にマッチする」という比較演算子である。反対に「正規表現にマッチしない」!~とあらわす。

if /sai?to/i = ~lineでは、検索パターンは「sai?to」である。これは「saito」もしくは「sato」をあらわす。//の後ろにあるi大文字と小文字を区別しないという意味を付加するもので、必要に応じてつけたり、つけなかったりする。iをつけない場合は「sato」はマッチするが、「SATO」はマッチしなくなる。

print line(3行目)

lineという変数には、ファイルから読み込んだ行が入っている。上記の条件に合致する場合に、その行が出力される。

end(4行目)

ifに対応するend。

end(5行目)

whileに対応するend。

出席課題

kensaku.rbプログラムの/sai?to/の部分を様々に変更して結果がどうなるか試してみる。これにあたり、まず/sai?to/の中を日本語にするとうまく実行できないことが確認しよう。

日本語にマッチさせるためには/の後ろにiの代わりにeまたはsをつける必要がある。

  • i:アルファベットの大文字と小文字を区別しない
  • e:日本語をEUC文字列だと仮定して照合(UNIXで作成したデータの場合)
  • s:日本語をShift-JIS文字列だと仮定して照合(Windowsで作成したデータの場合)

一通り試したら、下記の問題について考えてみる。

  1. カタカナで「ワタナベ」さんと「ワタベ」さんにマッチする正規表現はなにか
  2. アルファベットで3人のフジシマさんにマッチする正規表現はなにか
  3. 町ではなくて市に住んでいる人にマッチする正規表現は何か

制限時間は10分。完成しない場合は、途中まででも構わないので実行し、結果をメールで送ること。出席点は2点。提出要領は下記の通り。

  • 提出先:naoya@e.koeki-u.ac.jp
  • メールのSubject:学籍番号-ruby07
  • 本文の構成:1行目で学籍番号、氏名を記載する。2行目以降に3つの問題の答え(正規表現)を書く。時間があれば、各正規表現のパターンについて説明を加えてみる。

Tips:emacsでの日本語入力のオンオフはCtrl-oです

Tips:Mewによるメールの送り方はMewコマンドを参照

正規表現の指定方法

通常の文字によるマッチング

正規表現によるパターンが英数字のみで書かれている場合、単純に文字列の中にその文字が含まれているかどうかでマッチする、しないを判断する。以下にサンプルを示す。正規表現にマッチする文字列は黄色の網掛けで示し、マッチしている部分を赤色で示す。

正規表現:/ABC/(意味:ABCを含む文字列)

#=> ABCABCDEF123ABC、 A1B2C3、AB、abc

行頭(^)と行末($)のマッチング

/ABC/というパターンは、ABCを含む文字列であれば全てマッチする。しかし場合によってはABCから始まるのののみを検索したり、最後がABCで終わっているものを検索することもある。「abcdef」はマッチするが「123abc」はマッチしないようにするためには、先頭がabcで始まっているものとすればよい。「123abc」にマッチし、「abcdef」にマッチしないようにするには最後がabcで終わっているものという指定をすればよい。

このためには、「^」や「$」といった特殊な文字を使用する。これらの文字を総称してメタ文字と呼ぶ。「^」は行頭マッチング、「$」は行末マッチングをあらわす。

正規表現:/^ABC/(意味:ABCで始まる文字列)

#=> ABCABCDEF、 123ABC

正規表現:/ABC$/(意味:ABCで終わる文字列)

#=> ABC、 ABCDEF、123ABC

マッチさせたい文字を範囲で指定する

イトウもしくはゴトウのように「イかゴ」のどちらかを含むという条件を指定する場合、マッチさせたい文字の集合を[]で囲む。[]内のどれかが該当すればマッチすることになる。

正規表現:/[AB]/(意味:AまたはBを含む文字列)

#=> DNABookBAC、 Cat、DOT

正規表現:/[ABC]/(意味:A、B、Cのいずれかを含む文字列)

#=> DNABookBACCat、 DOT

正規表現:/[CBA]/(意味:A、B、Cのいずれかを含む文字列。上と同じ)

#=> DNABookBACCat、 DOT

正規表現:/[012ABC]/(意味:0、1、2、A、B、Cのいずれかを含む文字列)

このような書き方の場合、「大文字のアルファベットが含まれていればなんでもOK」というような正規表現を指定する場合、[]の中に26文字書かなければならなくなるため記載が大変になる。この場合は[]の中を○−○というようにハイフンを使って範囲で示すことができる。

正規表現:/[A-Z]/(意味:アルファベットの大文字を含む文字列)

#=> 028ABookCat、 dog、075、6-4

正規表現:/[a-z]/(意味:アルファベットの小文字を含む文字列)

#=>028A、 BookCatdog、 075、6-4

正規表現:/[0-9]/(意味:数字を含む文字列)

#=> 028A、 Book、Cat、dog、 0756-4

正規表現:/[A-Za-z]/(意味:アルファベットを含む文字列)

#=> 028ABookCatdog、 075、6-4

正規表現:/[A-Za-z_]/(意味:アルファベットとアンダーバーを含む文字列)

正規表現:/[A-Za-z_-]/(意味:アルファベットとアンダーバーとハイフンを含む文字列)

正規表現:/[ぁ-ん]/(意味:ひらがなを含む文字列「ぁ」は小文字)

正規表現:/[亜-腕]/(意味:漢字を含む文字列。ただし第一水準のみ)

ハイフンは範囲を示すために使用するので、ハイフン自体を検索対象の文字列としたい場合は、最初か最後に書かなければならない。

なお行頭マッチングで使用した^は[]内で使用するとそこで指定されたもの以外の文字という意味になる。例えば、[^ABC]はA、B、C以外の文字、[^a-z]はアルファベットの小文字以外の文字ということになる。

正規表現は組み合わせて使用することができる。ここまでに出てきたものを組み合わせると以下のようなものを作ることができる。

正規表現:/a[ABC]c/(意味:aとcの間にA、B、Cのいずれかがある文字列)

#=> aBc1aBcDe、 abc

正規表現:/a[^A-C]c/(意味:aとcの間がA、B、C以外の文字である文字列)

#=> aBcabca0cmalcolm、 aCc

正規表現:/[ABC][AB]/(意味:A、B、Cいずれかの次にAまたはBが続く文字列)

#=> ABCAAACCCCAxCBx、 CC、 CxAx、 C

正規表現:/[0-9][A-Z]/(意味:数字の次に大文字のアルファベットが続く文字列)

#=> 0A000AAA

正規表現:/[^A-Z][A-Z]/(意味:アルファベットの大文字以外の文字の次にアルファベットの大文字が続く文字列)

#=> aA2B3CNH068A

正規表現:/[^0-9][^A-Z]/(意味:数字以外の文字の次にアルファベットの大文字以外の文字が続く文字列)

#=> 1A2B

任意の文字とのマッチング

.(ピリオド):任意の1文字にマッチする。ピリオド1つなら1文字の文字列、3つなら3文字の文字列をあらわす。

正規表現:/A.C/(意味:AとCの間に何か一文字ある文字列)

#=> ABC012A3C456AA、 AC、 ABBC、 abc

正規表現:/aaa.../(意味:aaaの後に何か三文字続く文字列)

#=> 00aaabcde、 aaabb

バックスラッシュを使ったパターン

\s(バックスラッシュスモールエス):空白文字をあらわす。空白、タブ、改行文字とマッチする。

正規表現:/ABC\sDEF/(意味:ABCとDEFの間に空白が一つある文字列)

#=> ABC DEFABC\tDEFGH123ABC\nDEFG、 ABCDEF

\S(バックスラッシュラージエス):空白以外をあらわす。

正規表現:/ABC\SDEF/(意味:ABCとDEFの間に空白以外の文字が一つある文字列)

#=> ABC3DEF4GHI012-ABC-DEF、 ABCrubyDEF、 ABCDEF

\d(バックスラッシュディー):0から9までの数字とマッチする。

正規表現:/\d\d\d-\d\d\d\d/(意味:ハイフンの手前に数字が3桁、後ろに4桁ある文字列)

#=> 012-345601234-012345, ABC-DEFG, 012-21

\w(バックスラッシュダブリュー):英数字にマッチする。

正規表現:/\w\w\w/(意味:英数字が3つ連続で続いている文字列)

#=> ABCabc012, AB C, AB\nC

\A(バックスラッシュラージエー):文字列の先頭にマッチする。^との違いは、^は画面上に出力されている状態で先頭にあればマッチするが、\Aはひとかたまりの文字列の先頭であるかどうかを見る。具体的には、123\nABCは文字列の途中に改行文字が入っているが、/^ABC/はマッチするのに対し、/\AABC/はマッチしない。

正規表現:/\AABC/(意味:先頭がABCである文字列)

#=> ABCABCDEF、 012ABC、 012\nABC

\Z(バックスラッシュラージゼット):文字列の末尾にマッチする。$との違いは、$が画面上に出力されている状態で末尾にあればマッチするが、\Zはひとかたまりの文字列の末尾であるかどうかを見る。具体的には123\nABCは文字列の途中に改行文字が入っているが、/123$/はマッチするのに対し、/123\Z/はマッチしない。

正規表現:/ABC\Z/(意味:末尾がABCである文字列)

#=> ABC012ABC、 ABCDEF、 012\nABC、 ABC\nDEF

繰り返し

*:直前の文字の0回以上の繰り返しにマッチする。

正規表現:/A*/(意味:Aが何個あってもいいし、なくても良い)

#=> AAAAAA1BBB

正規表現:/A*C/(意味:Cの前にAが何個あってもいいし、なくても良い)

#=> AAACBC、 AAAB

正規表現:/AAA*C/(意味:Cの前にAが2個以上ある)

#=> 012\nAAC012AAAAAAAAC、 AC

正規表現:/A.*C/(意味:AとCの間に何かが何個あってもいいし、なくても良い)

#=> AB012CAB CDACDE

+:直前の文字の1回以上の繰り返しにマッチする。

正規表現:/A+/(意味:Aが1個以上連続している)

#=> AAAAAA、 1、 BBB

正規表現:/A+C/(意味:Cの前にAが1個以上ある)

#=> AAACBAC、 BC、 AAAB

正規表現:/AAA+C/(意味:Cの前にAが3個以上ある)

#=> AAACHAAAAAAC、 AAC、 AC

正規表現:/A.+C/(意味:AとCの間に何かが1個以上ある)

#=> AB012CAB CD、 ACDE

?:直前の文字の0回または1回の繰り返しにマッチする。

正規表現:/^A?/(意味:先頭がAでもAでなくても良い)

#=> BAAAAA book

正規表現:/A?C/(意味:Cの前にAがあってもなくても良い)

#=> ACAAAC、 BC、 C

正規表現:/AAA?C/(意味:Cの前にAが2個以上ある)

#=> AAAAACAAC、 AC

正規表現:/A.?C/(意味:AとCの間に何かがない、もしくは1文字ある)

#=> ACDEABCDE、 AB012C、 AB CD

()と繰り返し

():*+?と組み合わせて使用することで,1文字単位での繰り返しではなく、複数の文字列の繰り返しにマッチする。

正規表現:/^(ABC)+/(意味:ABCが1回以上繰り返されている)

#=> ABCABCABCABCABCABC、 BCA

正規表現:/HOTO(TO)?GISU/(意味:TOが0回もしくは1回)

#=> HOTOTOGISUHOTOGISU、 HOTOTOTOGISU

選択

:いくつかの候補の中からどれか1つに当てはまるものにマッチする。

正規表現:/(sakura|kashiwa)mochi/(意味:mochiの前にsakuraもしくはkasiwaがある)

#=> sakuramochikasiwamochi、 awabimochi、 yakimochi

複雑な正規表現のパターンを作る

メタ文字は組み合わせて使用することができる。幾つか例を示す。

  • /^...X\d\d/:先頭から4文字目がXでその後に数字が2つ続いている文字列
  • /^[A-Z].+rb$/:先頭が大文字でその後は何でも良くて最後にrbがついている文字列
  • /^\S\S\s+/:先頭が2文字でその後が空白になっている文字列

レポート課題

自分で自由にリストを作り、そのリストから特定の正規表現のパターンにマッチする行を取り出してみる。いろいろなパターンについて試し、その中でも複雑な5つのパターンについて実行した結果をレポートする。具体的には下記の流れにしたがうこと。

  1. 自分の興味があるもの(例:好きな野球選手と所属チーム、自動車の車種とメーカー、競走馬と調教師、小説と作家、ジュースとメーカー、聖闘士と必殺技)について、1行に1データづつ合計で10行以上作成し、data.txtというファイル名で保存する。
  2. 個人情報保護の観点から、サークルの名簿等、個人情報が含まれているものをそのまま利用しないように配慮すること。
  3. kensaku.rbの正規表現の部分を様々に変更し、どのような正規表現のパターンを試したらどのような結果が得られたかを説明する。様々なメタ文字を組み合わせて複雑な正規表現のパターン作ってみること。
  4. 1件もマッチするものがない正規表現のパターンはNGとする。必ず1件以上マッチするものを考えること。
  5. 正規表現のパターンを変える度にいちいちプログラムを書き換えるのが面倒だと感じる人は、キーボードから正規表現のパターンを入力できるように変更しても良い。ただしこれを自力で行うのはかなり難しいので強制はしない。まだ教えていないことを自分なりに勉強しないと作成できない。
  • 提出先:naoya@e.koeki-u.ac.jp
  • 提出期限:6/4(日)23:59(1限履修者)·6/11(日)23:59(2限履修者)
  • メールのSubject:学籍番号-kadai05
  • 本文の構成:1行目で学籍番号、氏名を記載する。2行目以降は下記の構成とする
  1. 作成したdata.txtとその説明(何のデータなのか)
  2. 作成したプログラム(作成した人のみ)
  3. プログラムの各部の説明(作成した人のみ)
  4. 使用した正規表現のパターンとそのパターンの説明、実行結果×5
  5. 感想

  • 採点基準:期限内提出点(2点)、メールの体裁(1点)、使用したメタ文字の種類(2点)、正規表現のパターンの説明(3点)
  • 使用するメタ文字は合計で10種類以上を目指すこと。正規表現のパターンを説明する場合は、個別のメタ文字の説明をするのではなく、自分でメタ文字を組み合わせて作成したパターンの説明をすること。
  • 説明に関して、文章の意味がわかりづらい場合や、Webページを単にコピー&ペーストしたものは減点することがある。一度読み直してから提出すること。
  • 驚異的に良くできているレポートについては満点の8点を超える得点をつけることがある。
  • よくできていたレポートは、他の人の参考になるよう、本人が特定できないような形で掲載する。掲載してほしくない場合はメールでの課題提出時にその旨記載すること。

Tips:emacsでの日本語入力のオンオフはCtrl-oです

Tips:ktermでのプログラムの実行結果をメールに貼り付けるには、コピーしたい箇所をマウスで選択し、emacs(Mew)上でマウスの真ん中ボタンをクリックする

Tips:Mewによるメールの送り方はMewコマンドを参照