roy > naoya > 基礎プログラミングI·情報検索 > (7)正規表現[1]

(7) 06/02の授業内容:正規表現[1]

正規表現とは

電話帳で「ミニマムバリュ」という店の電話番号を探す場合、50音順に並んでいるので「ミ」の場所をみれば良い。では「美作太郎商店」や「美篶形成外科」の電話番号はどのように調べればよいだろうか? 前者は「みさく」「びさく」のどちらだかわからないし(正解は「みまさか」)、後者については全く読めない(正解は「みすず」)。この様な場合は、先頭の文字から調べるのではなく、先頭はわからないが後ろが「太郎商店」や「形成外科」である店の番号という検索ができれば便利である。

電話で名乗られたときも同じようなことが考えられる。携帯電話から電話をしており電波状況が悪くうまく聞き取れない。おそらく「マエハシ」か「マエバシ」なのだが判断ができない。こんな場合は「マエ(ハまたはバ)シ」という名字の人という探し方ができれば便利である。「サトウ」と「サイトウ」の聞き分けが難しい場合には「サ(イ)トウ」という探し方が必要になるかもしれない。

何かを検索するときに、そのデータに含まれる文字列の一部やパターンを利用することがある。このパターンを表現するために使用するのが正規表現である。

正規表現とは

  • データに含まれる文字の一部やパターンを表現するために正規表現を用いる。

正規表現の例

「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{c10xxxx}%ruby kensaku.rb meibo.txt[Return]
砂糖真琴    サトウマコト        山形市    SATO Makoto 
齋藤由      サイトウユウ        大江町    SAITO Yu

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

while line=gets(1行目)

getsはキーボードから入力された値を文字列として取得するメソッドとして利用してきた。しかし、今回のプログラムを実行しても入力は求められなかったはずである。getsメソッドはキーボードからの入力を受け取る以外に、ファイル内容の読み込みも行うことができる。プログラム実行時にファイルを指定しなければキーボードからの入力を受け取り、ファイルを指定するとファイルの中味を読み込む。今回は実行時に、meibo.txtを指定しているのでこのファイルの中味を読み込む。

getsメソッドがキーボードからの入力を受け取る場合、入力後に必ず[Return]を押す。[Return]はUNIXでは\nという改行文字として受け取られる。getsメソッドがファイルから読み込みを行う場合も、同様に\nを区切りとしてとらえる。すなわち行末の改行を区切りとして1つのデータとして読み込み、ここでは変数lineに代入する。

while line=getsはデータを読みこめる間は繰り返し処理を行えという意味になる。meibo.txtは15行あるので、15回繰り返され、16回目になると読み込むデータがなくなるので繰り返しから抜ける。

if /sai?to/i = ~line(2行目)

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

/正規表現/ =~ 文字列

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

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

print line(3行目)

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

end(4行目)

ifに対応するend。

end(5行目)

whileに対応するend。

正規表現を用いたプログラムに関するまとめ

  • 外部ファイルを読み込んでプログラム内で用いる場合、ktermでruby プログラム名 読み込むファイル名として実行する。
  • 実行時に外部ファイルを指定した場合、getsメソッドはキーボードの入力を読み込むかわりにファイル内容を読み込む。
  • ファイルからの読み込み時は\nまでを1つのデータとして扱う。
  • =~は正規表現にマッチするという比較演算子、!~は正規表現にマッチしないという比較演算子
  • 正規表現のパターンの後ろのiは大文字と小文字を区別しないという意味。

正規表現のオプション

/正規表現/の後ろにiは大文字と小文字の区別をしないというオプションであるが、これは半角英数字を検索対象とする場合に有効なオプションである。日本語にマッチさせるためには/の後ろにiのかわりにeまたはsをつける必要がある。

正規表現のオプション

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

出席課題

meibo.txtにおいて以下の検索を行うための正規表現のパターンを考えてみよう。

  1. カタカナで「ワタナベ」さんと「ワタベ」さんにマッチする正規表現
  2. アルファベットで苗字の先頭がSで始まる人にマッチする正規表現
  3. 市ではなくて町に住んでいる人にマッチする正規表現

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

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

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

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

正規表現の指定方法

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

正規表現によるパターンが英数字や日本語のみで書かれている場合、文字列内にその文字が含まれていればマッチする。以下にサンプルを示す。正規表現にマッチする文字列は黄色の網掛けで示し、マッチしている部分を赤色で示す。

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

#=> ABCABCDEF123ABC、 A1B2C3、AB、abc

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

ABCから始まる文字列や、最後がABCで終わっている文字列を検索する場合は、「^」や「$」といった特殊な文字(メタ文字を使用する。「^」は行頭マッチング、「$」は行末マッチングをあらわす。

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

#=> ABCABCDEF、 123ABC

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

#=> ABC、 ABCDEF、123ABC

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

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

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

#=> DNABookBACCat、 DOT

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

#=> DNABookBACCat、 DOT

「大文字のアルファベットが含まれていればなんでもOK」というような正規表現を指定する場合、○−○というようにハイフンを使って範囲で指定することができる。

正規表現:/[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_-]/(意味:アルファベットとアンダーバーとハイフンを含む文字列)

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

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

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

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

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

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

#=> aBc1aBcDe、 abc

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

#=> aBcabca0cmalcolm、 aCc

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

#=> 0A000AAA、254XBJ

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

#=> aA2B3CNH068A

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

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

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

#=> ABC012A3C456AA、 AC、 ABBC、 abc

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

#=> 00aaabcde、 aaabb

繰り返し

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

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

#=> 012\nAAC012AAAAAAAAC、 AC

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

#=> AB012CAB CDACDE

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

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

#=> AAACBAC、 BC、 AAAB

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

#=> AB012CAB CD、 ACDE

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

正規表現:/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

バックスラッシュを使ったメタ文字

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

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

#=> ABC DEFABC\tDEFGH123ABC\nDEFG、 ABCDEF

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

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

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

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

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

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

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

正規表現:/\D\d\d\d\d\d\d\D/(意味:数字以外の文字の間に数字が6桁並んでいるもの)

#=> c106999a、 c1068887

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

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

#=> ABCabc012、 AB C, AB\nC

\W(バックスラッシュラージダブリュー):英数字と_(アンダーバー)以外にマッチする。

正規表現:/A\WB/(意味:AとBの間が英数字以外の文字列)

#=> A CA-C、 ABC、A9C

\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

\b(バックスラッシュスモールビー):単語の境界にマッチする。例えば/cat/で検索すると、concatenateやcategoryもマッチするが、\bcat\bにすると、単語として独立したcatしかマッチしない。

正規表現:/\bdog\b/(意味:dogという単語を含むもの)

#=> doghot dog、 dogberry

正規表現の特殊文字

\:ピリオド、[]、?、*、+、^、$、|、()などのメタ文字として使用されている文字を検索対象としたい場合は手前にバックスラッシュをつけ、\.、\[、\]、\?、\*、\+、\^、\$、\|、\(、\)とする。バックスラッシュを検索対象とする場合も\をつけ\\とする。

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

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

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

レポート課題

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

  1. 自分の興味があるもの(例:好きな野球選手と所属チーム、自動車の車種とメーカー、小説と作家、ジュースとメーカー)について、1行に1データづつ合計で10行以上作成し、data.txtというファイル名で保存する。ただし、個人情報保護の観点から、サークルの名簿等、個人情報が含まれているものをそのまま利用しないように配慮すること。
  2. kensaku.rbの正規表現の部分を様々に変更し、どのような正規表現のパターンを試したらどのような結果が得られたかを説明する。様々なメタ文字を組み合わせて複雑な正規表現のパターン作ってみること。
  3. 1件もマッチするものがない正規表現のパターンはNGとする。必ず1件以上マッチするものを考えること。
  4. 正規表現のパターンを変える度にいちいちプログラムを書き換えるのが面倒だと感じる人は、キーボードから正規表現のパターンを入力できるように変更しても良い。ただしこれを自力で行うのはかなり難しいので強制はしない。まだ教えていないことを自分なりに勉強しないと作成できない。
  • 提出先:課題提出用メールアドレス
  • 提出期限:6/8(日)23:00
  • メールの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コマンドを参照