roy > naoya > 基礎プログラミングII[月2] > (3)配列の復習とARGV
5回の課題の平均点を計算するケースについて考えてみる。このプログラムは既に学んでいる繰り返し表現を用いることで記述可能である(sample.rb)。
#!/usr/koeki/bin/ruby number = 0 times = 5 sum = 0 while number < times printf ("%d回目の得点を入力してください:",number+1) score = gets.chomp!.to_f sum += score number += 1 end average = sum / number printf ("平均点は%5.2f点です\n",average)
このプログラムを実行すると以下のようになる。ここで黄色の部分がユーザーが入力した値である。
irsv{naoya}% ruby sample.rb[Return] 1回目の得点を入力してください:30[Return] 2回目の得点を入力してください:40[Return] 3回目の得点を入力してください:50[Return] 4回目の得点を入力してください:60[Return] 5回目の得点を入力してください:70[Return] 平均点は50.00点です。
このプログラムではユーザーが入力した個別の得点を記憶しておくことができない。入力をした各回の得点は全てscoreに代入される。このため2回目の得点を入力すれば1回目の得点を忘れ、3回目の得点を入力すれば2回目の得点を忘れることになる。このプログラムでは各回の得点を覚えておく必要性はないが、前期の授業で取り上げたようなレジのプログラムでは、最後にレシートを作成する必要があり、個々の値を記憶させておく必要が生じる。このような場面で役に立つのが配列であった。
変数には通常1つの値しか代入しておくことができない。以下のケースでは、x = 10とすることで、以前xに代入されていた5は忘れてしまい、xの値は10となる。このように新しい値を代入すると、前に代入されていた値を忘れてしまうというのが通常の変数の特性である。
x = 5 print x,"\n" #=>5 x = 10 print x,"\n" #=>10
これに対して、通常の変数を拡張して、複数の値を入れられるようにしたものが配列である。
x = 150:これまでの変数(1つの値のみ保存可能)
y = [150, 200, 380, 160, 240, 400]:配列(複数の値を保存可能)
上記ではyという配列変数には6個の値が代入されている。配列変数は複数の値を,(カンマ)で区切って保存し、かつ全ての値を[]内に格納している。配列の中に代入されている値の呼び方について、単にyに代入されている値とすると、6個のうちのどれを指しているのかがわからなくなる。このため、配列変数を使用する場合は、配列内の何番目の値を表示するのか、または配列内の何番目に値を代入するのかを示す必要がある。
1番目からではなく0番目から開始していることに注意しよう。
次に0番目のデータや1番目のデータをRubyの記法にしたがって記述する方法を見てみよう。
配列の中の何番目の値であるかは、変数名[n]で表現することができる。一番最初の値であれば変数名[0]、2番目は変数名[1]となる。[]の中の数字のことを添字もしくはインデックスと呼ぶ。
sample.rbを配列を用いて書き直してみよう(sample2.rb)。
#!/usr/koeki/bin/ruby score = [] number = 0 times = 5 sum = 0 while number < times printf ("%d回目の得点を入力してください:",number+1) score[number] = gets.chomp!.to_f sum += score[number] number += 1 end average = sum / number printf ("平均点は%5.2f点です\n",average)
冒頭でscore = []とし、scoreが配列である旨宣言している。そして、sample.rbでscoreであった場所をscore[number]に変更している。[]内はインデックスを指定する場所であるが、ここではnumberという初期値が0の変数を用いている。このためscore[number]はscore[0]と書いてあるのと同義である。while-endの繰り返しを行う中でnumber+=1を行っているため、繰り返しを行うたびにnumberの値は1、2、3と1ずつ増加していく。これによりユーザが入力した値はscore[1]、score[2]、score[3]に順番に代入されることになる。
このプログラムを実行すると、結果はsample.rbと変わらないが、sample2.rbでは以下のように5回分の得点が全て記憶されている(30、40、・・・の数字はユーザが入力した値で、実際は何でも良い)。
score | ||||
[0] |
[1] |
[2] |
[3] |
[4] |
30 |
40 |
50 |
60 |
70 |
scoreを配列にすることで各回の得点を全て記憶しているということのメリットは、次のようなプログラムを書くことで明らかとなる。ここでは上記のプログラムに黄色の部分を追加している。この部分を追加することで各回の得点を全て表示することが可能となる。
#!/usr/koeki/bin/ruby
score = []
number = 0
times = 5
sum = 0
while number < times
printf ("%d回目の得点を入力してください:",number+1)
score[number] = gets.chomp!.to_f
sum += score[number]
number += 1
end
average = sum / number
1.upto(number) do |i|
printf ("%d回目の得点は%d点でした\n",i,score[i-1])
end
printf ("平均点は%5.2f点です\n",average)
配列処理メソッドとしてこれまで利用してきたのはlengthのみであったが、他にも多数のメソッドがある。ここでまとめてみよう。
ktermにおいてrubyプログラムを実行する際、
irsv{学籍番号}% ./prog.rb[Return]
とか
irsv{学籍番号}% ruby prog.rb[Return]
と入力した。
特殊な方法として、
irsv{学籍番号}% ruby prog.rb data.txt[Return]
のようにプログラム名の後ろにファイル名を指定すると、そのファイルを読み込ませ、プログラムの中で取り扱うことができた。
ktermからプログラムを実行する際に、プログラム名の後ろに値を指定することで、その値をプログラム内で使用することができる。前期はファイル名のみを指定したが、以下のように値を指定することもできる。
irsv{学籍番号}% ruby prog.rb 100 200 300[Return]
ktermからプログラムを実行する際に上記のように与えた値のことを引数(ひきすう)という。Rubyでは、プログラム実行時に与えた引数は自動的にARGVという配列変数に代入される。
上記の例では100、200、300という引数が与えられており、配列変数のARGVのインデックス0、1、2にそれぞれ
ARGV[0] = "100"
ARGV[1] = "200"
ARGV[2] = "300"
のように代入される。いずれも文字列であることに注意する必要がある。なお100 200 300の間はスペースのみでありカンマ等はないことに注意せよ。
これを利用すると、プログラム実行時に与えた値に基づいて動作を決定することができる。
ARGVについてまとめ
ktermからプログラムを実行する際に引数を指定すると、ARGVという配列変数に代入される
1からユーザが指定した任意の値までの足し算を行うプログラムを書いてみよう。これまでは、プログラム実行後に「好きな数字を入力してください」と表示し、キーボードから入力された値をgetsメソッドで取得するという以下のようなプログラムを書いてきた(sum-gets.rb)。
#!/usr/koeki/bin/ruby sum = 0 print "好きな数字を入力してください" goal = gets.chomp!.to_i 1.upto(goal) do |i| sum += i end printf ("1から%dまでの合計は%dです\n",goal, sum)
これをARGVを使用して書き換えることを考えてみよう。仮に30までの合計を求める場合、ktermでプログラムを実行する際に以下のように引数を与える必要がある(プログラム名はsum-ARGV.rbとする)
irsv{学籍番号}% ruby sum-ARGV.rb 30[Return]
プログラムは以下の通りとなる。ktermにおけるプログラム実行時に与えた引数である30はARGV[0]に代入される。文字列として取り扱われるため、goal = ARGV[0].to_iとし、整数に変換してからgoalに代入している。
#!/usr/koeki/bin/ruby sum = 0 goal = ARGV[0].to_i 1.upto(goal) do |i| sum += i end printf ("1から%dまでの合計は%dです\n",goal, sum)
sum-ARGV.rbを実行してみよう。実行するときに
とした場合、それぞれどのような結果になるだろうか。実行結果を確認した上で、なぜそのような結果になるのかを考えてみよう。
制限時間は10分。出席点は2点。提出要領は下記の通り。
Tips:emacsでの日本語入力のオンオフはCtrl-oです
Tips:Mewによるメールの送り方はMewコマンドを参照
sum-ARGV.rbでは、プログラム実行時に引数を与えることが、プログラムを正常に実行する上での前提条件となっている。引数を与えなければ正しく実行することができない。しかし、実行しても何も起こらないプログラムでは、ユーザーは何がおかしいのかを理解することができない。おそらく自分の実行の方法が間違っているのではなく、プログラム自体に誤りがあるのではないかと考えるはずである。この問題を解決するために、プログラムを改良してみよう。
#!/usr/koeki/bin/ruby
if ARGV[0] == nil
STDERR.print "1からプログラム起動時に指定した値までの合計を出します\n"
STDERR.print "50までの総和を出したい場合 ruby goukei-ARGV.rb 50と入力します\n"
exit(1)
end
sum = 0
goal = ARGV[0].to_i
1.upto(goal) do |i|
sum += i
end
printf ("1から%dまでの合計は%dです\n",goal, sum)
このプログラムでは上部の5行(黄色の部分)を追加している。これは1つのif文である。ifの横の条件としてARGV[0] == nilを指定している。nilとはデータがないという意味であり、ARGV[0]にデータがないということは、プログラムを実行する際に引数を与えていないということを意味する。この場合にendの前にある3行を実行する。
STDERR.printの2行:メッセージをディスプレイに出力する。このメッセージを出すことでユーザはプログラムの実行方法が間違っていたことがわかる。このプログラムに関して言えば、STDERR.printではなくprintでもよい。STDERR.printは出力先としてファイルとディスプレイのいずれも指定することができる場合に、ディスプレイを指定する際の記法である(詳細は前期のopenメソッドの回を確認しよう)。STDERR.printとすることでディスプレイに表示することを明示的に示していると考えておけばよい。
exit(1):exitはプログラムの強制終了をあらわす。このプログラムでは引数を入力せずにプログラムを実行した場合、プログラムを継続する意味がない。そこでexitを使用して強制的にプログラムを終了している。プログラムを終了させる場合、プログラムの実行がうまくいったかどうかをシステムに対して示すことができる。これを終了ステータスというが、プログラムがうまくいった場合は0を用い(exit(0))、うまくいかなかった場合は0以外とする(exit(1))決まりになっている。
ARGVを用いてプログラム実行時に引数を与えた場合、キーボードからの入力を受け取るためにgetsメソッドを用いると予想外の挙動を示す。このプログラムは実行時に与えた引数がvar1に代入され、プログラム実行後にキーボードから入力した値がvar2に代入される。そしてvar1とvar2を足した結果を出力する(gets-ARGV.rb)。
#!/usr/koeki/bin/ruby var1 = ARGV[0].to_i print "好きな数字を入力してください\n" var2 = gets.chomp!.to_i answer = var1 + var2 printf ("%d足す%dは%dです\n",var1, var2, answer)
このプログラムを実行すると以下のようになる。ここで黄色の部分がユーザーが入力した値である。
irsv{c10xxxx}% ruby gets-ARGV.rb 20[Return]
好きな数字を入力してください
gets-ARGV.rb:6:in `gets': No such file or directory - 20 (Errno::ENOENT)
from gets-ARGV.rb:6
実行するとエラーが出てしまう。エラーメッセージを見ると「No such file or directory-20」すなわち「20というファイル、ディレクトリはありません」というメッセージである。
前期の授業で正規表現を扱ったときのことを思い出そう。外部に検索対象となるテキストファイル(例えばpiyo.txt)を保存しておき、そのファイル内の検索を行って正規表現にマッチする行のみを表示させるようなプログラムを書いてきたが、こうしたプログラムを実行する際には、下のように実行するプログラム名の後ろに読み込むファイル名を指定した。そしてプログラム内に書かれたgetsメソッドは実行時に指定したファイルから1行ずつ読み込むという働きを行った。
irsv{c10xxxx}% ruby hoge.rb piyo.txt[Return]
gets-ARGV.rb内のgetsメソッドもこれと同じ働きをしている。プログラムを実行する際にプログラムの後ろに引数を与えているが、これを読み込むファイル名だと判断し、読み込もうとしたがファイルが見つからないというエラーが発生しているのである。このままでは都合が悪いので、var2 = STDIN.gets.chomp!.to_iとする。これによりキーボードからの読み込みが可能となる。
STDINは標準入力という意味で、STDIN.getsでgetsメソッドの読み込み元を標準入力にせよという指定になる。標準的な入力はキーボードから行われるので、このような指定を行うことで、キーボードからの入力が受け付けられるようになる。
以下のうちいずれかを選んで解答する。ARGVおよび配列処理メソッドを用いて記述すること。ARGV・配列処理メソッドを用いなかった場合はそれぞれ1点減点する。
問題1(8点満点):次のルールで採点が行われる競技の得点を求めるプログラム(mark.rb)を作成せよ。ただしXはktermでのプログラム実行時に引数として与えること。
問題2(10点満点):point.txtは、年間16戦で構成されるK2グランプリの結果である(名前は平成名前辞典から引用)。各レースでは順位に応じて
が与えられる。7位以降は0点であり、リタイヤは「-」と表示される。リタイヤも0点としてカウントする。獲得した得点に応じてランキングが決まるが、16戦全ての結果は使用しない。個人ごとに良い成績をマークした上位X戦のポイントの合計を求め、このポイントにより最終的な順位が決定される。結果は、
のように表示される。point.txtを見ると、全部で15名がエントリしているが、最終的に結果を表示するのは上位Y位までとする。
このような条件を満たすプログラムrace.rbを作成しなさい。なお、X、Yはktermでプログラムを実行する際に引数として与えること。
Tips:emacsでの日本語入力のオンオフはCtrl-oです
Tips:ktermでのプログラムの実行結果をメールに貼り付けるには、コピーしたい箇所をマウスで選択し、emacs(Mew)上でマウスの真ん中ボタンをクリックする
Tips:Mewによるメールの送り方はMewコマンドを参照