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

(8) 06/09の授業内容:正規表現[2]

正規表現のパターンをキーボードから入力

前回のプログラムのように複数のパターンの検索を行う場合には、毎回プログラムを書き換えるのではなく、キーボードからパターンを入力できた方が都合が良い。方法についてはキーボードから正規表現のパターンを入力する方法のページを参照すること。

出席課題

以下の5種類の正規表現にマッチする文字列を選択肢から選びなさい。マッチするものが1つもない場合もあるし、複数の文字列がマッチする場合もある。

  1. /Federal/
  2. /A.+n/
  3. /\b\S\S\S\S\S\S\S\b/
  4. /^[^A-G].+tion$/
  5. /k/

解答の選択肢

  1. National Aeronautics and Space Administration
  2. Federal Bureau of Investigation
  3. Central Intelligence Agency
  4. Japan Aerospace Exprolation Agency
  5. National Transportation Safety Board
  6. Federal Aviation Administration
  7. National Highway Traffic Safety Administration
  8. Federal Highway Administration
  9. Federal Emergency Management Agency
  10. 該当なし

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

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

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{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

このプログラムを実行してみると、確かに漢字とローマ字の名前だけが取り出されて表示されることがわかる。

irsv{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
irsv{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箇所のメタ文字にマッチする部位を後方参照で取り出してname1name2に代入し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{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とすると、小数点以下を切り捨ててしまうので、結果がおかしくなってしまう。

レポート課題

下のハイパーリンクは6月1日から20日までの家計簿データである(ランダムデータ)。このデータに基づき、以下を実行するプログラムを作成しなさい(kadai6.rb)。問題1(7点満点)、問題2(8点満点)、問題3(10点満点)のいずれかを選択すること。データが現実離れしていると感じる場合は適宜修正しても良い。

6月1日〜20日分の家計簿kakeibo.txt

  1. 支出全体に占める食費の割合(=エンゲル係数)を算出しなさい。
  2. 1番を実施した上で、20日現在での内訳別の支出と残額を表示しなさい。残額を計算するために、kakeibo.txtの1行目にある予算の右の金額もプログラム内で取り扱えるようにすること。
  3. 2番を実施した上で、20日時点での残額に応じたメッセージを表示できるようにしなさい。具体的には、前提条件として公共料金の支出は月末までないものとし、公共料金を除いた20日時点での支出合計と残額を比較し、以下の3種類の条件に応じた3種類のメッセージを表示できるようにしなさい。
    • このままのペースでお金を使うと赤字になる場合
    • このままのペースでお金を使うと収支トントンになる場合
    • このままのペースでお金を使うと黒字になる場合

問題1〜問題3の実行結果のイメージを示す。いずれもprintfの書式制御文字に桁指定の数字を入れて整えて出力してもよい(あくまでも実行結果として記載すべき項目を示したのみであり、項目さえ抜け落ちなく表示すれば、順番や表示方法は自由とする)。

問題1

irsv{c10xxxx}% ruby kdadai6.rb kakeibo.txt
20日時点での食費の総額は○○円、支出合計は♪♪円
エンゲル係数は13.4%です。

問題2

irsv{c10xxxx}% ruby kdadai6.rb kakeibo.txt
エンゲル係数は13.4%です。
20日時点での支出は内訳別に
食費○○円、娯楽費△△円、被服費××円、
交通費□□円、公共料金☆☆円です。
合計は♪♪円で、残りЯЯ円です。

問題3

irsv{c10xxxx}% ruby kdadai6.rb kakeibo.txt
エンゲル係数は13.4%です。
20日時点での支出は内訳別に
食費○○円、娯楽費△△円、被服費××円、
交通費□□円、公共料金☆☆円です。
合計は♪♪円で、残りЯЯ円です。
このままだと赤字です。節約しましょう。
  • 提出先:課題提出用メールアドレス
  • 提出期限:6/22(日) 23:00
  • メールのSubject:kadai06
  • 本文の構成:1行目で学籍番号、氏名を記載する。2行目以降は下記の構成とする
  1. 作成したプログラム
  2. プログラムの実行結果
  3. プログラムの説明
  4. 感想

  • 採点基準:期限内提出点(2点)、メールの体裁(1点)、プログラム(2点or3点or5点)、プログラムの説明(2点)
  • プログラムの説明は、正規表現と後方参照の部分を中心に行うこと。3番を選んだ場合はメッセージ表示に関連する部位も説明すること。説明が多くなる分には構わないので、説明すべき箇所の絞込みが出来ない場合は1行ずつ細かく説明すること。
  • わかりにくい説明や、Webページを単にコピー&ペーストしただけの説明は減点することがある。一度読み直してから提出すること。
  • 驚異的に良くできているレポートについては満点の8点(10点)を超える得点をつけることがある。
  • よくできていたレポートは、他の人の参考になるよう、本人が特定できないような形で掲載する。掲載してほしくない場合はメールでの課題提出時にその旨記載すること。

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

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

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