roy > naoya > 基礎プログラミングI·情報検索 > (8)正規表現[2]
前回のプログラムのように複数のパターンの検索を行う場合には、毎回プログラムを書き換えるのではなく、キーボードからパターンを入力できた方が都合が良い。方法についてはキーボードから正規表現のパターンを入力する方法のページを参照すること。
以下の5種類の正規表現にマッチする文字列を選択肢から選びなさい。マッチするものが1つもない場合もあるし、複数の文字列がマッチする場合もある。
解答の選択肢
制限時間は10分。完成しない場合は、途中まででも構わないので実行し、結果をメールで送ること。出席点は2点。提出要領は下記の通り。
Tips:emacsでの日本語入力のオンオフはCtrl-oです
Tips:Mewによるメールの送り方はMewコマンドを参照
前回のプログラム(kensaku.rb)では、外部ファイル(meibo.txt)から1行ずつ読み込んで正規表現のパターンにマッチしているか判定を行った。
meibo.txt
丘本秋子 オカモトアキコ 酒田市 OKAMOTO Akiko 高梁缶 タカハシカン 真室川町 TAKAHASHI Kan 仁科牧子 ニシナマキコ 最上町 NISHINA Makiko 藤嶌楓 フジシマカエデ 庄内町 HUJISHIMA Kaede 町村利郎 マチムラトシロウ 鶴岡市 MACHIMURA Toshiro :(以下略)
そして、プログラムを実行するとプログラム内に記載された正規表現のパターンに応じて、マッチするデータが出力された。以下は/sai?to/の検索結果である。
irsv{naoya}%ruby kensaku.rb meibo.txt[Return] 砂糖真琴 サトウ マコト さいたま市 SATO Makoto 齋藤由 サイトウ ユウ 下関 SAITO Yu
1行ずつ変数に読み込み、正規表現に該当する場合に変数内のデータを出力しているので、出力結果では該当する行がまるまる表示される。しかし検索結果として名前だけが欲しい場合もある。1行あたりの情報量が多い場合にはなおさら全て表示されてしまうとわずらわしく感じる。例えば、漢字が読めない可能性を考慮し、漢字氏名とローマ字氏名だけを取り出すというようなことはできないのだろうか。
このために使用するのが後方参照である。まずは、検索することは考えずに漢字とローマ字の氏名だけを取り出すことを考えてみよう(kensaku2.rb)。
#!/usr/koeki/bin/ruby while line=gets if /(\S+)\s+\S+\s+\S+\s+(\w+\s\w+)/ =~ line name1 = $1 name2 = $2 printf ("%-12s %s\n",name1,name2) end end
このプログラムを実行してみると、確かに漢字とローマ字の名前だけが取り出されて表示されることがわかる。
irsv{naoya}% ruby kensaku2.rb meibo.txt[Return] 丘本秋子 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
今回のプログラムのポイントは正規表現の行とその下に続く2行である。順番に確認しよう。
プログラムを読み込むとファイルから1行ずつ読み込みlineに代入する。そしてこの行で正規表現のパターンに読み込んだデータがマッチするか判定が行われる。ここで使用しているメタ文字は次の4種類である。
これらのメタ文字で表現されている正規表現のパターンと読み込んだデータの対応関係は次の図の通りであり、読み込んだ全ての行がマッチする。ここでは意図的に全ての行がマッチする正規表現を書いている。このプログラムの目的は漢字氏名とローマ字氏名を取り出すことであり、ここで正規表現にマッチしない行があれば、もちろんこれらを取り出すことができないからである。
ところで、/(\S+)\s+\S+\s+\S+\s+(\w+\s\w+)/には2箇所()がついている。正規表現のパターンを書く際に()をつけておくと、その()内のメタ文字にマッチした部位を後で取り出すことができる。これが後方参照である。
後方参照とは
後方参照は、正規表現のパターンにマッチしたデータから特定の部分を取り出す方法である。正規表現のパターンを記述する際に一部を()を使って記述しておくと、()でくくられた部分にマッチした部位を、()の順番に応じて$1、$2のように$数字の形で取り出すことができる。なお、この形式で取り出した場合、値の型は文字列となる。
1つ目の()内に書かれている\S+にマッチするのは漢字氏名である。そして2つ目の()に書かれている\w+\s\w+にマッチするのはローマ字氏名となる。
name1 = $1 name2 = $2 printf ("%-12s %s\n",name1,name2)
$1により1つ目の()にマッチする漢字氏名を取り出し、name1に代入している。2つ目の()は$2で取り出し、name2に代入している。そしてprintfメソッドでこれらの変数を出力している。なお%-12sのマイナスは左づめ、12は12桁で表示する(日本語は1文字で2桁消費する)という意味である。
では、特定のパターンの検索を行うプログラムであったkensaku.rbと、漢字氏名、ローマ字氏名を取り出すプログラムであるkensaku2.rbを合体し、検索をして漢字氏名とローマ字氏名を取り出すプログラムkensaku3.rbを作成してみよう。
#!/usr/koeki/bin/ruby while line=gets if /sai?to/i =~ line if /(\S+)\s+\S+\s+\S+\s+(\w+\s\w+)/ =~ line name1 = $1 name2 = $2 printf ("%-12s %s\n",name1,name2) end end end
irsv{naoya}% ruby kensaku3.rb meibo.txt[Return] 砂糖真琴 SATO Makoto 齋藤由 SAITO Yu
このプログラムでは2つのif文を入れ子にして使用している。1つ目のif文で読み込んだ行が/sai?to/という正規表現のパターンにマッチするか判定が行われ、満たさない場合には何もせず、満たす場合には2つ目のif文が適用される。2つ目のif文では、/(\S+)\s+\S+\s+\S+\s+(\w+\s\w+)/という正規表現にマッチさせているが、これは全ての行がマッチするように書かれており、()でくくった2箇所のメタ文字にマッチする部位を後方参照で取り出してname1、name2に代入しprintfメソッドで表示している。
学籍番号と得点が記された以下のファイルがある(data.txt)。このファイルから学籍番号と得点を読み込んでそれぞれデータとし、平均点および各人の得点の平均からの差を算出してみよう。
学籍番号 点数 c110001 45 c110002 52 c110003 38 c110004 60 c110005 44 c110006 67 c110007 50 c110008 57 c110009 41 c110010 45
今回も、先ほどのプログラムと同様に正規表現の後方参照を利用してデータを取得するが、最後に全員の学籍番号と得点を結果として出力しなければならない。通常の変数を使うと個別の学籍番号や点数を記憶することができないため、配列を使う必要がある。
配列を用いたプログラム(analysis.rb)を以下に示す。
#!/usr/koeki/bin/ruby #初期設定 number = [] #学籍番号を代入する配列 score = [] #得点を代入する配列 sum = 0 #合計を代入する配列 n = 0 #配列に使用するインデックス i = 0 #配列に使用するインデックス #学籍番号と得点の読み込みと合計の計算 while line = gets if /(\S+)\s+(\d+)/ =~ line number[n] = $1 score[n] = $2.to_f sum += $2.to_f n += 1 end end #個々人の得点と平均点との差 average = sum / n print "- 学籍番号 ------ 得点 ---- 差 - \n" print "\n" while i < n printf (" %-10s \t %3d \t %6.1f\n", number[i],score[i],score[i]-average) i += 1 end
このプログラムを実行すると以下の結果が得られる。実行する際には、読み込むファイル(data.txt)を指定するのを忘れないようにする。
irsv{naoya}% ruby analysis.rb data.txt[Return] - 学籍番号 ------ 得点 ---- 差 - c110001 45 -4.9 c110002 52 2.1 c110003 38 -11.9 c110004 60 10.1 c110005 44 -5.9 c110006 67 17.1 c110007 50 0.1 c110008 57 7.1 c110009 41 -8.9 c110010 45 -4.9
このプログラム中で使用されている正規表現のパターンは/(\S+)\s+(\d+)/である。(\S+)が学籍番号にマッチし、\s+はその後のスペースに、(\d+)は得点にそれぞれマッチする。その後、
number[n] = $1 score[n] = $2.to_f sum += $2.to_f n += 1
で、$1、$2で()にマッチした部位を取り出し、それぞれ配列に代入している。$1は学籍番号、$2は得点に相当する。代入する際にはnumber[n]やscore[n]などの配列を使用している。インデックスはnであり、nの初期値は冒頭で0を代入しているため、1回目はnumber[0]やscore[0]に代入される。while-endの繰り返しを行うたびにn+=1をしているため、インデックスに使用するnは繰り返しを行う中で、1、2、3、・・・と1ずつ増加する。
後方参照の定義でも述べたように、$1、$2の形で取り出したときの値の型は文字列になる。このままでは合計得点や平均点を算出することができないため、score[n] = $2.to_fというように$2の後ろに型変換メソッドをつけている。to_iではなくto_fとしているのは、平均点や、平均点からの差異を計算する際に小数点以下の値がでてくる可能性があるためである。to_iとすると、小数点以下を切り捨ててしまうので、結果がおかしくなってしまう。
あなたは店のオーナーである。4人のアルバイトを雇っているが、どうも人件費がかかりすぎるようで気になっている。そこで総売上に占める人件費の割合を15%以下にするという目標を定めた。リンク先のテキストは6月1日から6月20日の間の毎日の売上金額と4人のアルバイト時間を示したものである。時給は全員700円とする。
このテキストに基づき、以下を実行するプログラムを作成しなさい(kadai6.rb)。今回は2種類の課題を設定している。1番に比べて2番はやや難しい。1番を選択した場合は8点満点、2番を選択した場合は10点満点で採点する。どちらを実施しても良い。
6月1日〜20日の売上、アルバイト代のデータ(work.txt)
irsv{naoya}% ruby kdadai6.rb work.txt 6月20日時点での人件費の割合は18.5%です。 15%をオーバーしています。 21日以降の平日の予測売上は85010円 同期間の土日の予測売上は100230円です。 人件費の割合を15%におさめるためには 平日は14時間、土日は21時間に抑える必要があります。
Tips:emacsでの日本語入力のオンオフはCtrl-oです
Tips:ktermでのプログラムの実行結果をメールに貼り付けるには、コピーしたい箇所をマウスで選択し、emacs(Mew)上でマウスの真ん中ボタンをクリックする
Tips:Mewによるメールの送り方はMewコマンドを参照