コマンドでのデータの取り出し

プロンプトでデータ検索を行うには 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 に含まれる
AB に含まれる
/ /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 station
end
end

となる。実行すると

%./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 である。 trueegrep コマンドの -i と同じで、大文字小文字を区別しない、 さらに Ruby 言語での正規表現を表す方法 /検索文字パターン/ と同じ。 実行方法はこちら

regexp_stdin.rb を使った実行結果の例

実際に動かしてみよう。

/i.ata/ を探そうとする場合

%./regexp_stdin.rb station.dat 
検索パターン : i.ata
新潟新潟県にいがたNiigata
新発田新潟県しばたShibata
象潟秋田県きさかたSakikata

となる。iata の間に一文字入っている駅名が探し出された。

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 manymany 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 円加算されるとしよう。 走行距離は駅の間の距離を四捨五入して求める。

データは駅の名前と起点からの距離に関するものだから

  • 料金 = 初乗り + 走行距離 * 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