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)
このプログラムを実行すると以下のようになる。ここで黄色の部分がユーザーが入力した値である。
pan{c10xxxx}% 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 p x #=>5 x = 10 p x" #=>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 1.upto(number) do |i| printf("%d回目の得点は%d点でした\n",i,score[i-1]) end 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を配列にすることで各回の得点を全て記憶している。このためプログラム下部のuptoメソッドで書いてあるように、配列内に記憶された値をprintfで全て表示するといったことが可能となる。ここではインデックスを[i-1]としているが、iの初期値が1であるのに対して、配列のインデックスは0から始まるためである。
配列処理メソッドとしてこれまで利用してきたのはlengthのみであったが、他にも多数のメソッドがある。ここでは代表的なもののみ示す。その他のメソッドは配列処理メソッドの補足ページを参照すること。
<<メソッドは配列の末尾に要素を追加するメソッドであるが、配列のインデックスを指定しなくてもよいため使えるようにしておくと便利である。インデックスを指定した代入方法、指定しない代入方法で書いた同じプログラムを以下に示す。
#!/usr/koeki/bin/ruby data = [] 0.upto(4) do |i| print"好きな値を入力してください\n" data[i] = gets.chomp!.to_i end
#!/usr/koeki/bin/ruby data = [] 5.times do print"好きな値を入力してください\n" number = gets.chomp!.to_i data << number end
ktermにおいてrubyプログラムを実行する際、
pan{c10xxxx}% ./prog.rb[Return]
とか
pan{c10xxxx}% ruby prog.rb[Return]
と入力した。
特殊な方法として、
pan{c10xxxx}% ruby prog.rb data.txt[Return]
のようにプログラム名の後ろにファイル名を指定すると、そのファイルの内容を読み込み、プログラムの中で取り扱うことができた。
ktermからプログラムを実行する際に、プログラム名の後ろに値を指定することで、その値をプログラム内で使用することができる。前期はファイル名のみを指定したが、次のように値を指定することもできる。
pan{c10xxxx}% 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とする)
pan{c10xxxx}% 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は1から○までの足し算をするプログラムであったが、これを書き換えて△から○まで足し算するプログラムにしてみよう。つまり、ktermでプログラムを実行する際に2つの引数を与え、1つ目を開始値、2つ目を終了値として取り扱えるようにしよう。
制限時間は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行を実行する。
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)
このプログラムを実行すると以下のようになる。ここで黄色の部分がユーザーが入力した値である。
pan{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行ずつ読み込むという働きを行った。
pan{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点満点):次のルールで採点が行われる競技の得点を求めるプログラム(rating.rb)を作成せよ。ただしXはktermでのプログラム実行時に引数として与えること。
問題2(10点満点):情報処理学、物理学、人間工学の3科目の得点が記載されたexam.txtを読み込み、3科目の合計得点の上位X番目まで、
のように表示するプログラム(array-sort.rb)を作成せよ。Xはktermでのプログラム実行時に引数として与えるものとし、1未満や名簿の人数を超過した値を指定した場合はエラーメッセージを出してプログラムを強制終了すること。
問題3(11点満点):問題2と同じだが、合計が同一得点になった場合に、2位 200点、3位 200点ではなく、2位 200点、2位 200点というように同じ順位で表示できるようにせよ。これにあたり、exam.txtに1名分データを追加し、合計が他の人(誰でも良い)と同じになるように3科目に適宜得点を配分しておくこと。
問題2、3を実施するにあたり、ヒントを参考にしても良い。
Tips:emacsでの日本語入力のオンオフはCtrl-oです
Tips:ktermでのプログラムの実行結果をメールに貼り付けるには、コピーしたい箇所をマウスで選択し、emacs(Mew)上でマウスの真ん中ボタンをクリックする
Tips:Mewによるメールの送り方はMewコマンドを参照