プロンプトでデータ検索を行うには egrep というコマンドを用いた。 あいまいな条件で調べるには meta character を指定すれば良かった。
% egrep "知りたいあいまいな組み合わせ" データ
このようなタイプのプログラムの内部では、 いったん ARGV 配列に文字列を格納して処理していた。
データは、必要な部分だけ取り出すということではなく、 データのしまってある 1 行全てを表示した。
% egrep "Oh?ish?ida" station.dat 大石田 山形県 おおいしだ Oishida
実際使われている行き先検索などでは、必要な部分だけしか表示しない。 プログラムでデータのしまってある行から必要な情報を切り出しているからである。
行き先検索だけでなく、成績の入った名簿、住所録などデータはいろいろある。 それらの特徴は、 1 行に特有なデータが、列ごとに分類されて収納されている というところにある。
UNIX の egrep コマンドに相当するプログラムを作ってみよう。 egrep.rb:
#!/usr/koeki/bin/ruby
#coding: euc-jp
while station = gets getsで取りこんだ 1 行を変数 station に代入
if /a.a/i =˜ station
# p station
print station
end
end
を作る。 データを取り込む変数を station とする。
while 変数 = gets 変数にデータを 1 行ずつ gets する変数に入ったデータ 1 行の処理end
while -- end 文の中は if -- end が入っており、ここで検索と一致するかどうか調べている。
/A/ =˜ B | あいまいな検索パターン A が データ 1 行が入った変数 B に含まれる |
=˜ | A は B に含まれる |
/ /i | 大文字小文字は問わないで検索 |
検索パターンをプログラムに仕込んでおく方法なので、 データを探すには、
%./egrep.rb 検索するファイル名
として、実行する。
/ / の中身を変えていろいろ試してみよう。
日本語には文字コードがあった。UNIX で作ったデータならば、 文字コードは変更せずに使用することができた。 regexp_egrep_jp.rb
#!/usr/koeki/bin/ruby #coding: euc-jp while station = gets gets で取ってきた 1 行を変数 station に代入
if /る?かわ/ =~ station
# p station print stationendend
となる。実行すると
%./egrep.rb station.dat
新白河 | 福島県 | しんしらかわ | Shinshirakawa |
清川 | 山形県 | きよかわ | Kiyokawa |
狩川 | 山形県 | かりかわ | Karikawa |
なぜ「るかわ」あるいは「かわ」のつく駅名が検索されたのか、順を追って説明せよ。
/ / で EUC で探す、という意味である。 2 行目で文字コードを指定しているため、 Microsoft 社が使用する Shift JIS や、電子メールが使う文字コード JIS を使用したデータでは、この方法にさらに 工夫が必要である。 EUC, Shift JIS, JIS は漢字コードと呼ばれる。
kconv というライブラリを加え、to_euc method を使用する。
#!/usr/koeki/bin/ruby #coding: euc-jp require 'kconv' while 変数 = gets if /正規表現/ =~ 変数.toeuc p 変数.toeuc end end
メールを読ませるプログラムを作りたい場合、 どのように作ればよいか。それはなぜだろうか。
regexp_egrep.rb では、検索するパターンをプログラムの中に仕込んでおく必要がある。 今度は検索パターンをキーボードで入力して探すように変更したい。 Regexp method を使おう。 regexp_stdin.rb
#!/usr/koeki/bin/ruby
#coding: euc-jp
STDERR.print("検索パターン : ")
pattern = STDIN.gets.chomp
変数 pattern にキーボード入力で取ってきた調べたい文字列をしまう
request = Regexp.new(pattern, true)
変数 request に /調べたい文字列/i を代入する。
while station = gets
if request =~ station
# p station
print station
end
end
STDIN データ処理の方法をキーボードからの入力により行う
STDIN は StanDard INput (標準入力) の略である。 キーボードで操作するという意味である。
Regexp.new(変数, オプション) 正規表現したい文字列にオプションをつける。
chomp
は、
文字列を切り取るという method である。
true
は
egrep
コマンドの -i
と同じで、大文字小文字を区別しない、
さらに Ruby 言語での正規表現を表す方法
/検索文字パターン/
と同じ。
実行方法はこちら
実際に動かしてみよう。
/i.ata/ を探そうとする場合%./regexp_stdin.rb station.dat
検索パターン : i.ata
新潟 | 新潟県 | にいがた | Niigata |
新発田 | 新潟県 | しばた | Shibata |
象潟 | 秋田県 | きさかた | Sakikata |
となる。i と ata の間に一文字入っている駅名が探し出された。
Regexp method の中に検索する文字列を入れてしまうので、 regexp_egrep.rb での / / の中だけを入力すればよい。日本語の検索の場合も同じである。
%./regexp_stdin.rb station.dat
検索パターン : 島根?県
新白河 | 福島県 | しんしらかわ | Shinshirakawa |
郡山 | 福島県 | こおりやま | Koriyama |
福島 | 福島県 | ふくしま | Fukushima |
( ) を使うと多彩な組み合わせによる検索が可能である。
(\S)\1
を試してみよう。
\S
は空白文字以外を探し、
\1
は
()
に出てくるものを 1 回繰り返すという意味である。
二つ合わせて 2 文字同じ並びのものを探す、
という意味である。
%./regexp_stdin.rb station.dat
検索パターン : (\S)\1
大宮 | 埼玉県 | おおみや | Omiya |
新潟 | 新潟県 | にいがた | Niigata |
大石田 | 山形県 | おおいしだ | Oishida |
羽前前波 | 山形県 | うぜんぜんなみ | Uzenzennami |
北余目 | 山形県 | きたあまるめ | Kitaamarume |
と出力される。 ひらがなだけでなくアルファベットが繰り返されているものも検索された。
( )\数字( )...( )\Nで、N番目の( ) と等しい文字列の置き換え
(so|many)\1
ならば、
so so
および
many many
が検索される。
(so|many)(so|many)
の場合、上記の二つのほか、さらに
so many
や
many so
までも検索される。
さらに、例えば
「レレレのおじさん」「ゲゲゲの鬼太郎」を取りだすには、
(\S)\1\1
である。
実際にデータを作成し、取り出して、なぜそうなるかを解説せよ。
列のデータには同じカテゴリーのデータが含まれている。 station.dat なら、駅名、都道府県、かな、アルファベットと並んでいる。 県に存在し、 ma または ta で終わる地名を探すには
%./regexp_stdin.rb station.dat
検索パターン : (\S+県)\s+(\S+)\s+(\S+[mt]a)$
新潟 | 新潟県 | にいがた | Niigata |
小山 | 栃木県 | おやま | Oyama |
郡山 | 福島県 | こおりやま | Koriyama |
福島 | 福島県 | ふくしま | Fukushima |
高畠 | 山形県 | たかはた | Takahata |
山形 | 山形県 | やまがた | Yamagata |
村山 | 山形県 | むらやま | Murayama |
新発田 | 新潟県 | しばた | Shibata |
酒田 | 山形県 | さかた | Sakata |
象潟 | 秋田県 | きさかた | Kisakata |
秋田 | 秋田県 | あきた | Akita |
升形 | 山形県 | ますかた | Masukata |
東酒田 | 山形県 | ひがしさかた | Higashisakata |
文字列の切り取りを復習しておこう。
\S+ |
空白文字以外の文字列が 1 個以上並んでいるものを探す |
\s+ |
空白文字が 1 個以上並んでいるものを探す |
これを組み合わせ、
県 空白 よみがな 空白 アルファベット [mt]a$
を作った。 遠い位置にあるデータの条件とを組み合わせて検索することができるようになる。 データの列と空白文字列を分解するとき、 () を使う。
% ./regexp_stdin.rb station.dat
検索パターン : (\S+県)\s+(\S+)\s+(\S+[mt]a)$
と書くことにする。次にこのデータの列を切り出す方法を学ぶ。
上野駅から出発する。 最後の駅が no で終わる駅を探したい。 このように東京都に属する駅を除いて駅名を探すときにこれが使える。 実際試し、何駅が検索されるか調べよ。
データ部分を ( ) で囲うと、必要な情報だけを取り出すことができた。 onzen あるいは onsen (温泉) に行きたいという客のためには
% ./regexp_cutout.rb station.dat
検索パターン : on[sz]en
かみのやま温泉駅
あつみ温泉駅
と駅名だけが出力された方が分かりやすい。このようにするために、 切り出すプログラム部分を作ろう。 regexp_cutout.rb を作る。 regexp_stdin.rb を少し変更して作る。
#!/usr/koeki/bin/ruby
#coding: euc-jp
STDERR.print("検索パターン : ")
pattern = STDIN.gets.chomp
request = Regexp.new(pattern, true)
while station = gets
if request =~ station
# print station
if /(\S+)\s+(\S+)\s+(\S+)\s+(\S+)/ =˜ station
printf("%s駅\n", $1)
end
end
end
実行し、上のように出力されるか確かめよう。
外国人向けに、駅名を英語で出力するにはどうしたらよいか。
データをいちいち指定しなければならないのは面倒なので、
プログラムに指定しておくことにする。regexp_cutout.rb から
regexp_readdata.rb
を作る。open -- end を用い、ファイルを指定しする。
データを開いて (OPEN) 読み込むのでモードは r
(Read)
である。
#!/usr/koeki/bin/ruby
#coding: euc-jp
STDERR.print("検索パターン : ")
pattern = STDIN.gets.chomp
request = Regexp.new(pattern, true)
open("station.dat","r") do |candidate|
while station = candidate.gets
if request =˜ station
# print station
if /(\S+)\s+(\S+)\s+(\S+)\s+(\S+)/ =˜ station
printf("%s駅\n", $1)
end
end
end
end
実行は
% ./regexp_readdata.rb
だけでよい。
open("ファイル名","モード") do |ファイル変数| : ファイル変数 に対して行う処理 : end
open -- endを一般に File Open と呼ぶ。 ファイル変数は、 open("ファイル名","モード") で開いた場所を教える変数である。
if A =˜ B -- end が、候補を探すことを学んだ。 インターネットの検索でも、オプション検索機能とし、 キーワードを除いて検索することが可能である。これは
if A !˜ Bもしも検索パターン A が B でなければ : end
を使う。
regexp_readdata.rb で =˜ を !˜ に入れ替えた結果、"山形" を検索するとどのような結果になるか。それはなぜか。
候補の駅を出力したあと、ユーザに選んでもらうようにしたい。 キーボード操作を簡単にするため、候補の数字を選ぶように作る。 regexp_readdata.rb から regexp_select.rb を作る。
#!/usr/koeki/bin/ruby
#coding: euc-jp
name = Array.new候補の駅名を選ぶ
STDERR.print("検索パターン: ")
pattern = STDIN.gets.chomp
request = Regexp.new(pattern, true)
open("station.dat","r") do |candidate|
i = 0候補を数える
while station = candidate.gets
if request =˜ station
# print station
if /(\S+)\s+(\S+)\s+(\S+)\s+(\S+)/ =˜ station
name[i] = $1
printf("%d \t %s駅\n", i+1, name[i])
i += 1
end
end
end
end
実行すると
%./regexp_select.rb 検索パターン : on[sz]en 1かみのやま温泉駅 2あつみ温泉駅
となる。name[i] に i+1 番目の候補が格納されている。 出力された候補の中から ユーザが選ぶ ように作るにはこちら。
配列 name の添字を選ばせ、表示すればよい。従って、今のプログラムの下に
STDERR.print("駅番号を選んで下さい\n")
j = gets.chomp.to_i
printf("%s駅を選択しました\n",name[j-1])
を加えればよい。実行すると
%./regexp_select.rb 検索パターン : on[sz]en 1かみのやま温泉駅 2あつみ温泉駅 駅番号を選んで下さい 2 あつみ温泉駅を選択しました
となる。name[i] に i+1 番目の候補が格納されている。
rikuusaisen.dat を使って、特急券用の自動券売機を作ってみよう。 write_ticket.rb
データを読み込ませて駅名を表示し、出発地と当着地を選ぶ。 初乗り料金と距離に応じた料金が加算される。 仮に初乗り料金は 120 円、 1 km あたり 20 円加算されるとしよう。 走行距離は駅の間の距離を四捨五入して求める。
データは駅の名前と起点からの距離に関するものだから
まずはデータの読み込ませ部分と表示部分を作る。
#!/usr/koeki/bin/ruby
#coding: euc-jp
stop = Array.new
dist = Array.new
i = 0
open("rikuusaisen.dat","r") do |f|
while line = f.gets
if /(\S+)\s+(\d+.\d+)/ =˜ line
stop[i] = $1
dist[i] = $2.to_f
i += 1
end
end
end
k = 0
printf("駅候補\n")
while k < stop.length
printf("%d: %-10s\t%4.1f\n", k+1, stop[k], dist[k])
k += 1
end
\d+.\d+ 数値列 何か 1 文字 数値列 すなわち「小数値」
続いて 出発駅と当着駅を選ぶ部分を作ろう。
続いて駅を選択させ、走行距離を求めよう。 発着の走行距離が負になるときは、絶対値に変換する。
print("駅番号を選んで下さい\n")
print("出発駅: ")
dept_n = gets.chomp.to_i
print("到着駅: ")
arvl_n = gets.chomp.to_i
distance = dist[dept_n] - dist[arvl_n]
if distance < 0
distance = -1 * distance
end
走行距離が負になる場合はどのような場合か、考えてみよう。
走行距離を四捨五入して、料金を求めていこう。
p0 = 120 # 初乗り運賃
grid = (distance + 0.5).to_i # 四捨五入した走行距離
printf("料金: %d 円\n", p0 + grid * 20) # 乗車運賃
最後に、券売機から 印刷する 部分を作ろう。
なぜ上記の方法により、四捨五入になるのか、そのしくみを考えよ。
自動的に印刷を行うには open -- end
を使う。ただし書き込みモード w
にする。
open("ファイル名","w") do |ファイル変数|
ファイル変数.実行文
end
r, w
の他に
a
モードがある。
ここで File Open のモードについてまとめておこう。
r | Read | 読みこみモード |
w | Write | 書きこみモード |
a | Append | 追加書きこみモード |
open("ticket.txt","w") do |ticket|
ticket.printf("%s -- %s \n走行距離: %4.1f[km]\n",
stop[dept_n], stop[arvl_n], distance)
ticket.printf("料金: %d 円\n", p0 + grid * 20)
end
print("発券終了\n") # 印刷されたかの確認を標準出力
実行した結果を cat して確かめよう。
%cat ticket.txt
古口駅 -- 南野駅
走行距離: 21.9 [km]
料金: 560 円
データの確かめを行う。
%bc -l 38.9 - 17.0古口から南野まで 21.9 21.9 + 0.5四捨五入 22.4 120 + 22 * 20初乗りに走行距離とキロあたりの運賃 560
陸羽西線に新駅を作ることになり、場所の選定を任された。 距離の長い駅間を新駅の候補地として報告することにした。 まずは平均距離を求め、そのあと各駅間の距離を求めよう。 read_average.rb
stop = Array.new
dist = Array.new
i = 0
while line = gets
if /(\S+)\s+(\d+\.\d+)/ =˜ line
stop[i] = $1
dist[i] = $2.to_f
i += 1
end
end
avg = (dist[dist.length-1] - dist[0])/dist.length
printf("平均距離: %4.1f[km]\n", avg)
配列 データを指定して読み込ませるプログラムにした。
stop に駅名、
配列 dist ]
に距離をそれぞれ代入した。
最後に平均の距離を変数 avg
に代入し、printf
文で実行結果を出力している。
次に、
各駅間の距離
を求めよう。
2 点間の距離を表示させる部分を付け加えよう。
n = 1
distance = dist[n]-dist[n-1]
while n < dist.length
printf("%s -- %s: %4.1f\n", stop[n], stop[n-1], distance)
n += 1
end
全ての 2 点間の距離を求めることができた。 平均値より大きい距離のところだけ出すには、分岐文を加えればよい。
if distance > avg
printf("%s -- %s: %4.1f[km]\n", stop[n], stop[n-1], distance)
end