roy > naoya > 基礎プログラミングI·情報検索 > (8)正規表現[2]
(8) 06/09の授業内容:正規表現[2]
[1] 正規表現のパターンをキーボードから入力
[2] 出席課題
以下の5種類の正規表現にマッチする文字列を選択肢から選びなさい。マッチするものが1つもない場合もあるかもしれないし、複数の文字列がマッチする場合もある。正規表現を読む練習なので、プログラムを実行せず、自力で解いてみよう。
- /National/
- /^[^A-J].+tion$/
- /\b\S\S\S\S\S\S\S\b/
- /A.+n/
解答の選択肢
- National Aeronautics and Space Administration
- Federal Bureau of Investigation
- Central Intelligence Agency
- Japan Aerospace Exprolation Agency
- National Transportation Safety Board
- Federal Aviation Administration
- National Highway Traffic Safety Administration
- Federal Highway Administration
- Federal Emergency Management Agency
- 該当なし
制限時間は10分。完成しない場合は、途中まででも構わないので実行し、結果をメールで送ること。出席点は2点。提出要領は下記の通り。
- 提出先:課題提出用メールアドレス
- メールのSubject:ruby08
- 本文の構成:1行目で学籍番号、氏名を記載する。2行目以降に問題1〜5の解答を記載する。時間があれば、各正規表現のパターンについて説明を加えてみる。
[3] 正規表現の後方参照
前回のプログラム(kensaku.rb)では、外部ファイル(meibo.txt)から1行ずつ読み込んで正規表現のパターンにマッチしているか判定を行った。
meibo.txt
丘本秋子 オカモトアキコ 酒田市 OKAMOTO Akiko 高梁缶 タカハシカン 真室川町 TAKAHASHI Kan 仁科牧子 ニシナマキコ 最上町 NISHINA Makiko 藤嶌楓 フジシマカエデ 庄内町 HUJISHIMA Kaede 町村利郎 マチムラトシロウ 鶴岡市 MACHIMURA Toshiro :(以下略)
そして、プログラムを実行するとプログラム内に記載された正規表現のパターンに応じて、マッチするデータが出力された。以下は/sai?to/の検索結果である。
pan{c10xxxx}%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
このプログラムを実行してみると、確かに漢字とローマ字の名前だけが取り出されて表示されることがわかる。
pan{c10xxxx}% 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行である。順番に確認しよう。
if /(\S+)\s+\S+\s+\S+\s+(\w+\s\w+)/ =~ line
プログラムを読み込むとファイルから1行ずつ読み込みlineに代入する。そしてこの行で正規表現のパターンに読み込んだデータがマッチするか判定が行われる。ここで使用しているメタ文字は次の4種類である。
- \S:空白以外にマッチ
- \s:空白にマッチ
- \w:英数字にマッチ
- +:1回以上の繰り返しにマッチ
これらのメタ文字で表現されている正規表現のパターンと読み込んだデータの対応関係は次の図の通りであり、読み込んだ全ての行がマッチする。ここでは意図的に全ての行がマッチする正規表現を書いている。このプログラムの目的は漢字氏名とローマ字氏名を取り出すことであり、ここで正規表現にマッチしない行があれば、もちろんこれらを取り出すことができないからである。
ところで、/(\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
pan{c10xxxx}% 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メソッドで表示している。
[4] 正規表現の後方参照を用いたデータ処理
学籍番号と得点が記された以下のファイルがある(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"
while i < number.length
printf(" %-10s \t %3d \t %6.1f\n",
number[i],score[i],score[i]-average)
i += 1
end
このプログラムを実行すると以下の結果が得られる。実行する際には、読み込むファイル(data.txt)を指定するのを忘れないようにする。
pan{c10xxxx}% 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とすると、小数点以下を切り捨ててしまうので、結果がおかしくなってしまう。
[5] レポート課題
下のハイパーリンクは基礎プログラミングIVという架空の授業におけるAさんの出欠・レポート点のデータである。このデータに基づき、次のプログラムを作成しなさい(report6.rb)。
授業の出欠とレポート点(問題1用)(result1.txt)
- 出欠:出席2点、遅刻1点、欠席0点として得点を記載
- レポート:得点を記載。0点は課題がそもそもないか、未提出の場合
授業の出欠とレポート点(問題2・3用)(result2.txt)
- 出欠:出席2点、遅刻1点、欠席0点とする
- レポート:なし、未提出は得点を与えない
- 問題1(7点満点):出席点とレポート点をあわせた合計得点を求め、合計得点と評価を表示する。
- 問題2(8点満点):出席点とレポート点をあわせた合計得点を求め、合計得点と評価を表示する。
- 問題3(9点満点):出席点とレポート点をあわせた合計得点を求め、合計得点と評価を表示した上で、より高い評価を得るために何点不足しているか表示する。
評価基準はいずれも、以下の通りとする。
- 秀:90点以上
- 優:80点以上、90点未満
- 良:70点以上、80点未満
- 可:60点以上、70点未満
- 不可:60点未満
今回は、1名のデータを読み込み対象とするが、仮に他の人のデータを読み込んだ場合でも、得点に応じた結果が表示できるようにしておくこと。
問題1〜問題3の実行結果のイメージを示す。ただし、項目さえ抜け落ちなく表示すれば、順番や表示方法は自由とする。
問題1
pan{c10xxxx}% ruby report6.rb result1.txt[Return] 合計得点は69点です。評価は「可」です。
問題2
pan{c10xxxx}% ruby report6.rb result2.txt[Return] 合計得点は69点です。評価は「可」です。
問題3
pan{c10xxxx}% ruby report6.rb result2.txt[Return]
合計得点は69点です。評価は「可」です。
評価「良」を得るためには 1点不足しています。
評価「優」を得るためには11点不足しています。
評価「秀」を得るためには21点不足しています。
#評価が「可」であるため、「評価「可」を得るためには・・」
というメッセージは表示されない。
提出要領
- 提出先:課題提出用メールアドレス
- 提出期限:6/14(日) 23:00
- メールのSubject:report06
- 本文の構成:1行目で学籍番号、氏名を記載する。2行目以降は下記の構成とする
- 作成したプログラム
- プログラムの実行結果
- プログラムの説明
- 感想
採点要領
- 採点基準:期限内提出点(2点)、メールの体裁(1点)、プログラム(2点or3点or4点)、プログラムの説明(2点)
- プログラムの説明は、正規表現と後方参照の部分を中心に行うこと。
- わかりにくい説明や、Webページを単にコピー&ペーストしただけの説明は減点することがある。一度読み直してから提出すること。
- 驚異的に良くできているレポートについては満点の8点(10点)を超える得点をつけることがある。
- よくできていたレポートは、他の人の参考になるよう、本人が特定できないような形で掲載する。掲載してほしくない場合はメールでの課題提出時にその旨記載すること。