Last-modified:2018/03/29 22:55:17.
実用的なプログラムを作ろうとするならば、文字列を上手く処理することが必要になってくる。 たとえば カンマ区切りの数値データファイルから数値データを取り出すには、 まず行として取得される文字列に対してカンマを処理するという操作が必要になる。 今回はまず、String クラスで使える便利な機能を見ていこう。 1
まず、今回の演習で使うファイルを作る。
#generateNumberData.rb
io = File.open("numberData.txt", 'w')
10000.times do |i|
io.printf("%04d\n", i) #出力用に4桁確保し、最初の桁などが空ならば0を詰める
end
io.close
これを実行すると numberData.txt
というファイルができている筈だ。
文字列 "1234"
というデータが含まれているか見てみよう。
#stringCompare.rb
io = File.open("numberData.txt", 'r')
array = io.readlines #ファイルの全内容を1行ごとに配列に格納
io.close
array.each do |i|
i.chomp! #ファイル読み込みなので、行末の改行文字を削除する必要あり
if ("1234" == i)
print "1234 is exist\n"
end
end
前述の単純な比較では、対象の文字列全体が一致する必要がある。 しかし「文字列 "123"
が含まれているもの」という判定を行いたい場合がある。 "123"
という文字の並びを含むデータがどれくらいあるか見てみよう。
#stringContain.rb
io = File.open("numberData.txt", 'r')
array = io.readlines #ファイルの全内容を1行ごとに配列に格納
io.close
array.each do |i|
i.chomp! #ファイル読み込みなので、行末の改行文字を削除する必要あり
if (/123/ =~ i)
print "/123/ is contained in #{i}\n"
end
end
ここで if
文で使われている /123/
という表現に注目して欲しい。 この //
で囲むデータ形式は「正規表現」と呼ばれるもので、 対象の文字列を判定するのに非常によく使われるものだ。
さて、if
文の比較演算子は少し変わっていて、「=~
」になっている。 これは正規表現のみに使われる比較演算子で、 パターンマッチングにはこれを使わなければならない。 もし通常の比較演算子「==
」を使った場合、 左右両辺のクラスが違うので全て偽の値を返すことになる。
前節の例は文字列のどこかに 123
という部分が含まれていることを チェックしたわけだが、 その文字列がある位置の情報も含めて正規表現を作ることができる。
文字列の頭に 123
がある場合(= その文字列が 123
から始まる場合)は、 さきのコードの /123/
のかわりに /^123/
とカレット「^
」を含めて書けば良い。
文字列の末尾に 123
がある場合(= その文字列が 123
で終わる場合)は、 さきのコードの /123/
のかわりに /123$/
とドル記号「$
」を含めて書けば良い。
例えば「12
で始まり4
で終わる」文字列であるかを判定したいとする。 まず「/^12/
」と「/4$/
」を使えば良いことは分かるだろう。 そしてその間は「任意の文字列」を表す正規表現を入れてやれば良い。 具体的には、「/^12.*4/
」のようにする。 この 「/./
」は「任意の1文字」を表し、 「/*/
」は「直前の表現の0回以上の繰り返し」を表すので、 この組み合わせである「/.*/
」は「0文字以上の任意の文字列」となる。
先の節では任意の文字列をマッチングさせたが、 実際にマッチした部分を取り出して利用したいことがある。 その場合には以下のように、括弧 ()
でその部分を囲めば $1
にその括弧内でマッチした部分文字列が格納される。
#stringCompareRegExp.rb
io = File.open("numberData.txt", 'r')
array = io.readlines #ファイルの全内容を1行ごとに配列に格納
io.close
array.each do |i|
i.chomp! #ファイル読み込みなので、行末の改行文字を削除する必要あり
if (/^12(.*)4$/ =~ i)
print "/^12(.*)4$/ is matched with #{i}: $1 == #{$1}\n"
end
end
正規表現は強力で多くのことができる反面、使いこなすのが難しい。 2 ここでは軽く触れる程度にとどめたが、正規表現はスルメのように噛めば噛むほど味が出る。 また正規表現は様々なプログラミング言語で使われているので、 基本を押えておくと多くの状況で対応できる。 ただし複雑で微妙なマッチングでは、言語間で微妙な差異があったりするので、 そのような場合はキチンと調べる必要がある。
Ruby の文字列(String)クラスは様々な機能が用意されている。 ここで全てを紹介することはできないが、代表的なものを挙げておこう。 まず、文字列の一部を書き換える操作(String.sub 系)だ。
#stringSub.rb
str = "abcdabcd"
p str.sub("cd", "CD") #=> "abCDabcd"
p str #=> "abcdabcd"
str = "abcdabcd"
p str.sub!("cd", "CD") #=> "abCDabcd"
p str #=> "abCDabcd"
str = "abcdabcd"
p str.gsub("cd", "CD") #=> "abCDabCD"
p str #=> "abcdabcd"
str = "abcdabcd"
p str.gsub!("cd", "CD") #=> "abCDabCD"
p str #=> "abCDabCD"
sub は substitute (置換する)の略である。 上で示したコードでは sub, sub!, gsub, gsub!
という4種類のメソッドが含まれている。 1つずつ見ていこう。
1つめの sub
は最初にマッチした文字列 "cd"
を "CD"
に置換している。 しかし作用させた変数 str
自体は変化せず、 通常はこれを何かの変数に代入するなどして使用する。
2つめの sub!
は基本的には sub
と同じで、 文字列 "cd"
を "CD"
に置換しているのだが、 作用させた変数 str
自体も変化する。
3つめの gsub
は文字列に含まれる全ての "cd"
を "CD"
に置換する。 作用させた変数 str
自体は変化しない。 なお、 gsub
の g
は global という意味。
4つめの gsub!
は文字列に含まれる全ての "cd"
を "CD"
に置換し、 作用させた変数 str
自体が変化する。
なお、ここでは置換対象として文字列 "cd"
を第1引数として与えたが、 これを正規表現で与えることも可能である。
str = "abcdabcd"
p str.sub(/c.*d/, "CD") #=> "abCD"
p str #=> "abcdabcd"
キーボード入力やファイル入力によって "12 34 56"
といった文字列データが得られたとしよう。 これを配列に格納するにはどうすれば良いか?
1つの文字列 "12"
を数値 12
に変換するためには、 以前に見たように String.to_i
を作用させれば良い。 しかし "12 34 56"
は空白文字で区切られた3つの数値となるべき 部分文字列で構成されているので、直接 String.to_i
を作用させることができない。 なので、まずはこの部分文字列を分解して配列に入れてやるべきだ。
#stringSplit.rb
str = "12 34 56"
p array = str.split(" ") #区切り文字。正規表現も使用可能。
array.each do |i|
p i.to_i
end
ここで、 i.to_i
で文字列から整数への変換を行っているが、 配列に入っているデータそのものは書き変わっていないことに注意が必要だ。 数値の配列を使いたければ、自前で受け入れ先の配列を作っておいて Array.push
してやるなどの処理が必要だ。
コメントを書く必要がないほど、意図が明解で見通しの良いコードを書けるならばコメントは不要だが、そうでない多くの人 3 は適宜コメントを入れておいた方が無難だろう。
プログラマの間には「3日前のソースは他人のソース」という言葉がある (ソースとはソースコード(プログラムコード)のこと)。 人間 1週間も見なければ、何故そのようなコードを書いたのか忘れてしまうものだ。 コードを読むより日本語を読む方が得意な人は、コメントを多めに付けておいた方が良い。
繰り返し使うデータは変数に格納して利用するのが 変更に強いプログラムを作るための第一歩だ。 一般に、2回以上使う値(文字列を含む)は変数や定数にした方が良いだろう。
変数に格納して利用するということには、 修正個所をソースファイル先頭に持って来るという効果もある。 修正箇所をソースファイルから探す手間を大幅に減らすことができる。
また、タイプミスを防ぐという意味もある。 3.14159265358979 を 3.14159265357989 と書き間違ってしまっても、 その間違いは大変発見し難い。 3.14…… が出る度に目を皿のようにしてチェックするのは、かなりやりたくない仕事だ。
変数名は内容を反映したものを付けること。
a = 60
b = 60
c = 24
d = 7
print a*b*c*d
これでは何やってるプログラムか分からない。 以下のような変数名、コメントを付すくらいの配慮は欲しい。
min = 60 #秒
hour = 60 #分
day = 24 #時間
week = 7 #日
print min * hour * day * week, "秒\n"
字下げについては既に説明した。 Ruby においては半角の空白文字や改行文字は基本的には無視されるため、 行頭の位置を調整することで一塊のブロックを視覚的に把握しやすくするということだ。
まとまりの強い個々のブロックの間に空行を挿入することも有効だ。 空行の使用方法は各自適当に工夫してみよう。 ただし使いすぎると却って見難くなってしまうので注意のこと。
加算・減算・乗算と違って、除算では致命的なエラーが生じることがある。 有限の数値はゼロで割ることができないので、 「divided by 0 (ZeroDivisionError)
」のようなエラーメッセージを吐いて プログラムがストップしてしまう。 4
たとえばベクトルを長さで規格化しようとする時にベクトルの長さで割る、 平均を取るときに要素数で割る、といったことは頻繁にあるが、 プログラムに与えられたデータによってはそれらがゼロになることもありうる。 割り算が必要になるときには、毎回分母が0になりうるかどうかを考える習慣をつけよう。
2010年6月2日は 日本では「2010/06/02」のように表すことが多いが、 アメリカでは「June 2nd, 2010」のように表す。 そこでアメリカ式の任意の日付を日本式に変換するプログラムを作成せよ。 ただし、入力ファイルとしてサーバの第8回課題データファイルの 自分の学籍番号のファイルを扱い、 (学籍番号)-out.txt という名前のファイルに出力するようにすること。 その際、月の名前は大小文字を区別しないようにせよ。 そのための正規表現は、たとえば /january/i
のように 2つめのスラッシュの後に i を加えれば良い。
今回提示したお作法に注意し、これまで自分が作成し、 提出した全てのコードを書き直せ。 さらに書き直したポイントを箇条書にて簡潔にまとめよ。