正規表現のパターンは//でくくって示した。
/パターン/
前回の課題では特定のパターンにマッチする行を検索するために、毎回パターンを書き直して保存をし、ktermで実行をするという手順をとった。このようにプログラム中に直接パターンを記入する方法は、様々なパターンを試してみる場合には使い勝手が悪い。ここでは、キーボードから正規表現のパターンを入力し、そのパターンにマッチする行を表示するという方法について考えてみよう。このためには、もう1つの正規表現のパターンの指定方法を把握する必要がある。
reg= Regexp.new("[AB]C+")
これは[AB]C+を正規表現のパターンとし、このパターンをregという変数に代入するという表記方法である。実際にプログラムを作成して確認してみよう(reg.rb)。
#!/usr/koeki/bin/ruby reg = Regexp.new("[AB]C+") p reg
これを実行すると以下の通りとなる。
irsv{naoya}% ruby reg.rb /[AB]C+/
pメソッドは変数内の値を表示するメソッドである。配列内の値の格納状況を調べるために使用したが、配列以外の変数にも利用することができる。プログラムを実行するとpメソッドが変数regの中身を表示し、/[AB]C+/という結果を返す。
つまりRegexp.new("パターン")とすることで、()内は正規表現のパターンとなる。上記のプログラムでは("")内に[AB]C+という具体的なパターンを指定しているが、この部分は変数にすることができる。つまり、キーボードからパターンを入力して適当な変数に代入しておき、Regexp.new("")で("")内にその変数を指定すれば、キーボードから入力された正規表現のパターンで検索をすることが可能になるわけである。この考えに基づき、前回のプログラムを変更してみよう。変更箇所を黄色で示す。
#!/usr/koeki/bin/ruby STDERR.print "検索パターンは?:" reg = STDIN.gets.chomp! expression = Regexp.new(reg, true, "e") while line=gets if expression =~ line print line end end
続いてプログラムの変更点について解説する。
キーボードから入力された値を読み込み、改行を取り除いてregという変数に代入する。文字列のままでよいのでto_iをつけて整数には変換していない。この行ではgetsのまえにSTDINという見かけないものがついているが、これは標準入力という意味を持つ。前回のプログラムは実行する際に
irsv{naoya}% ruby kensaku.rb meibo.txt
というように、検索対象となるファイルを指定した。そして、getsメソッドはファイル内を1行ずつ読み込むという作業を行っていた。今回のプログラムでは、検索をするデータは外部ファイルから読み込み、検索パターンはキーボードから読み込む。つまり、データを読み込む元が2種類あることになる。プログラムを実行する際にはgetsメソッドがどこからデータを読み込むのかを指定しないとコンピュータはどうすればよいかわからず困ってしまう(プログラムが意図したとおりに動かなくなるので、実際に困るのはコンピュータではなくプログラマ)。これを解決するために以下の方法が採用される。
標準的には入力元はキーボードとなるため、ファイル、キーボードの双方からgetsメソッドで値を読み込む必要がある場合は、キーボードから読み込む場合に、標準入力であることを示すSTDINをつけSTDIN.getsと標記する。
この行の1つ上に、STDERR.print "検索パターンは?:"という行があり、ここでもSTDERRという見慣れないものがついている。これもSTDINの場合と考え方は同じで、printが""内を表示する先はディスプレイ以外に、ファイルを指定することができる。今回は指定したファイルの中身を読み込むだけであるが、来週は指定したファイルに結果を出力する方法についても学ぶ。出力先もディスプレイとファイルの2種類になるので、標準的な出力先であるディスプレイに表示する場合はprintではなく、STDERR.printを使用する。なお、今回のプログラムに限定すれば、ファイルに結果を書き出すようには書かれていないため、出力先はディスプレイのみとなる。このためSTDERRはなくてもよい。ただし、不要な場合につけていると間違いということではない。むしろディスプレイに表示することを明示的に示すことができるので、プログラムを書いた本人以外の人がプログラムを読む場合は、書き手の意図が伝わりやすい。
先ほどは()の中に[AB]C+と直接入力していたが、ここではregという変数になっている。regには1つ上の行でキーボードから入力されたパターンが代入されている。reg以外にtrueや"e"と記載されているが、これらはそれぞれ下記の意味を持つ。
第2引数
true:大文字と小文字を区別してほしくない場合。/パターン/iと同じ意味。
false:大文字と小文字を区別してほしい場合。
第3引数(日本語の検索をしたいときに指定)
"e":EUCコードで書かれているものとして照合。/パターン/eと同じ意味。
"s":Shift-JISコードで書かれているものとして照合。/パターン/sと同じ意味。
101/102教室で英数字を検索する場合
Regexp.new(変数, true)
Regexp.new(変数, false)
101/102教室で日本語を検索する場合
Regexp.new(変数, true, "e")
Regexp.new(変数, false, "e")
前回のkensaku.rbではexpressionのかわりに/sai?to/iというように具体的なパターンが入力されていた。ここをexpressionという変数にすることで、キーボードから入力した任意のパターンに対するマッチングの有無を調べることができる。
以下の5種類の正規表現にマッチする文字列を選択肢から選びなさい。マッチするものが1つもない場合もあるし、複数の文字列がマッチする場合もある。
解答の選択肢
制限時間は10分。完成しない場合は、途中まででも構わないので実行し、結果をメールで送ること。出席点は2点。提出要領は下記の通り。
Tips:emacsでの日本語入力のオンオフはCtrl-oです
Tips:Mewによるメールの送り方はMewコマンドを参照
レジを通して入手されたデータは時系列で保存される。例えば以下のような形で保存されていたとする(regi.txt)。ここで同一時刻に購入されていれば、同じ人が購入したということになる(とする)。
売上日 時間 商品名 金額 仕入額 2006/06/05 13:12 みかん 298 100 2006/06/05 13:12 豆腐 78 20 2006/06/05 13:12 卵 198 60 2006/06/05 13:12 キャベツ 100 70 2006/06/05 13:12 さんま 58 10 2006/06/05 13:12 大根 128 45 2006/06/05 13:15 砂糖 128 60 2006/06/05 13:15 生姜 100 30 2006/06/05 13:18 せんべい 258 80 2006/06/05 13:18 おにぎり 100 38 2006/06/05 13:18 から揚げ 300 120 2006/06/05 13:18 コロッケ 198 50 2006/06/05 13:23 牛乳 158 55 2006/06/05 13:23 ニンジン 128 40 2006/06/05 13:23 玉ねぎ 198 65 2006/06/05 13:23 はたはた 178 30 2006/06/05 13:23 ごま油 158 80 2006/06/05 13:23 トマト缶 100 25
売り上げの時系列データに基づいて、何が売れやすいのか、何時頃に売れやすいのか、ペアで買われやすいものは何か等、経営戦略を練る上で有用な情報を引き出すことをデータマイニングという。現時点では様々な分析をするのは難しいので、今回はデータマイニングもどきを実践してみよう。
具体的には、売り上げ額−仕入額を計算して、商品別に利益額を算出し、合計や平均値を計算する(実際には、ここから人件費や光熱費、広告費などを引かなければならないため、利益はさらに少なくなる)。
続いて以下のプログラムをmining.rbとして保存する。
#!/usr/koeki/bin/ruby #初期設定 name = [] #商品名を代入する配列 price = [] #金額を代入する配列 cost = [] #仕入額を代入する配列 profit = 0 #利益額を代入する配列 n = 0 #配列に使用するインデックス i = 0 #配列に使用するインデックス #商品名と金額の読み込みと利益額の計算 while line = gets if /\S+\s+\S+\s+(\S+)\s+(\d+)\s+(\d+)/ =~ line name[n] = $1 price[n] = $2.to_i cost[n] = $3.to_i profit += $2.to_i - $3.to_i n += 1 end end #個々の利益額の表示 print "- 商品名 ------- 利益 --\n" print "\n" while i < n printf ("%-10s \t %3d円\n", name[i],price[i]-cost[i]) i += 1 end print "-------------------------\n" printf ("利益(合計) %5d円\n",profit) printf ("利益(平均) %5d円\n",profit/n)
このプログラムを実行すると以下の結果が得られる。実行する際には、読み込むファイル(regi.txt)を指定するのを忘れないようにする。
irsv{naoya}% ruby mining.rb regi.txt - 商品名 ------- 利益 -- みかん 198円 豆腐 58円 卵 138円 キャベツ 30円 さんま 48円 大根 83円 砂糖 68円 生姜 70円 せんべい 178円 おにぎり 62円 から揚げ 180円 コロッケ 148円 牛乳 103円 ニンジン 88円 玉ねぎ 133円 はたはた 148円 ごま油 78円 トマト缶 75円 ------------------------- 利益(合計) 1886円 利益(平均) 104円
プログラムの実行結果を見ると、みかんや豆腐などが表示されている。これらはregi.txt内に記載されていたものであり、どうやらこのプログラムでは読み込んだテキストから必要な部分を取り出しているということがわかる。読み込んだテキストから必要な部分だけを取り出す方法を正規表現の後方参照という。このプログラムはで新しく出てきたのは以下の2行となるが、いずれも後方参照を行う上で把握しておかなければならない記載方法である。
後方参照は、正規表現でマッチした文字列から特定の部分を取り出す方法である。正規表現のパターンを記述する際に一部を()を使って記述しておくと、()でくくられた部分にマッチした部位を、()の順番に応じて$1、$2のように$数字の形で取り出すことができる。
これを念頭に置きながら新しく出てきた2行について見てみよう。
まずここで使用しているメタ文字について確認しよう。
これらのメタ文字によって表現されるパターンは日本語で表すと、次のように表現できる。
空白以外の文字が何個かあり、空白が何個かあり、空白以外の文字が何個かあり、空白が何個かあり、(空白以外の文字が何個かあり)、空白が何個かあり、(数字が何個かあり)、空白が何個かあり、(数字が何個かある)
ここで、()でくくり赤色で書かれている部分が、上記の正規表現のパターンで()内に書かれているメタ文字に相当する。
そして、この正規表現のパターンはregi.txtに書かれている各行の記載内容にマッチする。()でくくられた3つの部分はそれぞれ黄色で網掛けされた部分に該当する。
2006/06/05 13:23 トマト缶 100 25
正規表現のパターンを記述する際に()を使うと、()内のメタ文字にマッチした部分を$1や$2で取り出すことができる(後方参照)。$1は1つ目の()である(\S+)にマッチする文字列である商品名(トマト缶)、$2は2つ目の()である(\d+)にマッチする文字列である金額(100)、$3は3つ目の()である(\d+)にマッチする文字列である仕入額(25)が代入されている。これをname[n] = $1やprice[n] = $2.to_iの行において、nameやpriceなどの配列変数に代入している。[n]の部分はインデックスで、具体的な値を用いずに初期値をn=0とし、whileの繰り返しの中でn+=1をすることでインデックスを1ずつ増やしながら配列内に順次値を代入している。なお、外部ファイルであるregi.txtはgetsメソッドで1行ずつ読み込んでいるため、このままでは文字列となってしまう。後で計算をするため、ここではprice[n] = $2.to_iというようにto_iメソッドをつけて整数に変換している。
mining.rbを改良する。改良の方法は以下の2種類であり、1番と2番のいずれかを選んで実施する。1番は比較的易しく、2番は驚異的に難しい。このため、1番を選んだ場合は7点満点、2番を選んだ場合は8点満点で採点する。
- 時刻 ------- 購入額 -- 13:12 XXX円 13:15 YYY円 13:18 ZZZ円 : : ---------------------- 合計 TTTT円 平均 AAA円
今回のプログラムの改良を行うにあたり、1番を選んだ場合は正規表現のパターンを変更する必要がある。変更を忘れるとregi.txtを1行ずつ読み込んでも正規表現にマッチする行が全くなくなってしまうので注意すること。2番を選んだ場合は後方参照すべき場所が異なってくる。時刻が同一の場合に金額を加算していく方法が思いつけば、全く太刀打ちできないというほどのものでもない。
Tips:emacsでの日本語入力のオンオフはCtrl-oです
Tips:ktermでのプログラムの実行結果をメールに貼り付けるには、コピーしたい箇所をマウスで選択し、emacs(Mew)上でマウスの真ん中ボタンをクリックする
Tips:Mewによるメールの送り方はMewコマンドを参照