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

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

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

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

出席課題

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

  1. /National/
  2. /A.+n/
  3. /\s+\S\S\S\S\S\S\S\s+/
  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点。提出要領は下記の通り。

  • 提出先:naoya@e.koeki-u.ac.jp
  • メールの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{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行である。順番に確認しよう。

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{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箇所のメタ文字にマッチする部位を後方参照で取り出して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{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

  1. 6月1日から20日までの総売上に占めるアルバイト代の人件費の割合を計算し、12%未満であれば「まだまだ余裕がある」、12〜15%の場合は「ちょうど良いペースである」、15%を超える場合は「少し切り詰める必要がある」というようなメッセージを表示できるようにせよ。なおメッセージの文章自体は適宜変更してよい。データを変更し、実際に望んだ結果がでるか確認すること。
  2. 6月1日から20日の売上に基づき6月21日から30日の売上を予測しなさい。そして6月期のアルバイト代が総売上に占める割合を15%にするためには、今後、毎日のアルバイトの総時間(=4人の合計時間)を何時間にしていけばよいか計算して表示しなさい。なお6月21日から30日の間には平日が7日、土日が3日ある。平日の予測売上は6月1日から20日の平日の売上の平均値、土日の予測売上は、同期間中の土日の売上の平均値を利用すること。実行結果は例えば以下のようになる(ただし数字は適当)。結果表示方法は自由に設定してよいが、以下の結果の例に示すような内容を含むこと。
irsv{naoya}% ruby kdadai6.rb work.txt
6月20日時点での人件費の割合は18.5%です。
15%をオーバーしています。
21日以降の平日の予測売上は85010円
同期間の土日の予測売上は100230円です。
人件費の割合を15%におさめるためには
平日は14時間、土日は21時間に抑える必要があります。
  • 提出先:naoya@e.koeki-u.ac.jp
  • 提出期限:6/17(日) 23:59
  • メールのSubject:kadai06
  • 本文の構成:1行目で学籍番号、氏名を記載する。2行目以降は下記の構成とする
  1. 作成したプログラム
  2. プログラムの実行結果
  3. プログラムの説明
  4. 感想

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

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

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

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