(6) 5/22の授業内容:配列

配列への導入

前回はregister.rbを改良し、税込みの合計金額を表示したり、金銭の授受が行えるようにしたりした。しかし、これだけではまだレジとして不十分である。レジであれば本来はレシートが発行されなければならない。レシートを作成するためには、個々の商品の金額を覚えておく必要があるが、現在のプログラムでは覚えておくことはできなかった。では、どうすれば購入した全ての商品の金額を覚えておくことができるだろうか?

1つ目の方法は、購入した商品の数に相当する変数を準備しておくということである。例えば、item1、item2、item3、item4,....,item10などとしておくことができる。最初に入力した金額はitem1に代入し、2番目に入力した金額はitem2に代入、ということを延々と繰り返せば全ての商品の金額を覚えておくことができる。

しかし、お客さんが何個商品を購入するかは既知ではない。このためあらかじめ何個変数を準備しておけばよいかを決めることもできない。このような問題を解決するために利用するのが配列である。

配列とは

変数には通常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個の値が代入されている。最大6個まで保存できるということではなく、より多くの値を保存できる。配列変数は複数の値を,(カンマ)で区切って保存し、かつ全ての値を[]内に格納している。配列の中に代入されている値の呼び方について、単にyに代入されている値とすると、6個のうちのどれを指しているのかがわからなくなる。このため、配列変数を使用する場合は、配列内の何番目の値を表示するのか、または配列内の何番目に値を代入するのかを示す必要がある。

  • 150:yの中の0番目のデータ
  • 200:yの中の1番目のデータ
  • 380:yの中の2番目のデータ
  • 160:yの中の3番目のデータ
  • 240:yの中の4番目のデータ
  • 400:yの中の5番目のデータ

1番目からではなく0番目から開始していることに注意しよう。

次に0番目のデータや1番目のデータをRubyの記法にしたがって記述する方法を見てみよう。

  • yの中の0番目のデータ:y[0]
  • yの中の1番目のデータ:y[1]
  • yの中の2番目のデータ:y[2]
  • yの中の3番目のデータ:y[3]
  • yの中の4番目のデータ:y[4]
  • yの中の5番目のデータ:y[5]

配列の中の何番目の値であるかは、変数名[n]で表現することができる。一番最初の値であれば変数名[0]、2番目は変数名[1]となる。[]の中の数字のことを添字もしくはインデックスと呼ぶ。

配列の使い方

プログラムの中で配列を使用する場合、まずはその変数が配列変数であることを示す(定義する)必要がある。

その変数が配列であることを定義する方法

  1. x = []
  2. x = array.new

1と2のうちどちらの方法でも良い。

配列への値の代入と、代入された値の表示

まず、その変数が配列であることを定義する。代入をする場合は、配列の何番目に代入をするかインデックスで指定をする。一つの例を見てみよう(array.rb)。

#!/usr/koeki/bin/ruby

item = []  #=>itemは配列
sum = 0

print "1つ目の商品の金額を入力してください\n"
item[0] = gets.chomp!.to_i  #=>itemの0番目に代入
p item     #=>pは変数内の値を表示するメソッド
sum += item[0]

print "2つ目の商品の金額を入力してください\n"
item[1] = gets.chomp!.to_i
p item
sum += item[1]

print "3つ目の商品の金額を入力してください\n"
item[2] = gets.chomp!.to_i
p item
sum += item[2]

printf ("購入した商品は、%d円、%d円、%d円でした\n",item[0],item[1],item[2])
printf ("合計は%d円です\n",sum)

p:変数の中に代入されている値を表示してくれるメソッド。数字であれば、100というように""なしで表示し、文字列の場合は"39"や"情報"のように""をつけて表示してくれる。配列を指定すると[100、300、200]のように配列内の全ての値を表示してくれるので、配列の中に値がどのように格納されているのかを確認する上で便利なメソッドである。

このプログラムでは3つの商品を購入し、個々の商品の金額と合計金額を最後に表示する。個々の商品について金額を入力する際、item[0] = gets.chomp!.to_iというように、itemという配列変数に値を代入しているため、2つ目の金額を入力しても1つ目を忘れない。1つ目の金額はitem[0]に、2つ目はitem[1]というようにインデックスの値を1ずつ増やすことで、配列の中に順番に値が格納されていく。

配列内の値を表示は、printf ("購入した商品は、%d円、%d円、%d円でした",item[0],item[1],item[2])で行われている。ここでも具体的にインデックスを指定することで、配列に格納されている値を順番に取り出している。

配列には文字も代入できる

配列には数字だけでなく文字列も代入できる(通常の変数でも文字列を代入可能)。1つの配列変数の中に文字列と数字を混在させることもできる。

name = ["伊藤", "木村", "山田", "鈴木", "川島"]
mixed = ["Hello", 34, "Windows", "Green", 125]

インデックスの指定を間違えると値が上書きされる

先ほどのプログラム(array.rb)でインデックスの指定を間違えた場合にはどうなるだろうか。3つの商品の金額を入力する際、item[0]、item[1]、item[2]としていたが、全てitem[0]にしたらどうなるだろうか。

出席課題

array.rbのプログラムについて以下の変更を行う。その上で実行してみるとどのような結果になるだろうか。結果を確認した上で、なぜそのような結果になるのかを考えてみること。

  • item[1] = gets.chomp!.to_iをitem[0] = gets.chomp!.to_iに変更
  • item[2] = gets.chomp!.to_iをitem[0] = gets.chomp!.to_iに変更

制限時間は10分。完成しない場合は、途中まででも構わないので実行し、結果をメールで送ること。出席点は2点。提出要領は下記の通り。

  • 提出先:naoya@e.koeki-u.ac.jp
  • メールのSubject:学籍番号-ruby06
  • 本文の構成:1行目で学籍番号、氏名を記載する。2行目以降にまずktermでの実行結果を貼り付ける。その下にそのような結果になった理由をわかる範囲で記載する。

Tips:emacsでの日本語入力のオンオフはCtrl-oです

Tips:Mewによるメールの送り方はMewコマンドを参照

インデックスを変数にして代入する

配列を用いることで、前回作成したregister.rbのプログラムにおいて、最後に個々の商品の金額を表示することが可能となる。これによりレシートを作る可能性が高まる。ただし、これでも1つ問題が残る。それは配列に値を代入するにはインデックスを指定しなければならないということである。

array.rbではitem[0]、item[1]、item[2]というようにインデックスを指定していた。これをregister.rbに転用する場合、インデックスを何番まで準備しておけばよいかという問題に突き当たる。適当に100個分くらい準備しておくというはやはりスマートではないので、別の方法を考えよう。

この問題は、インデックスを変数にすることで解決することができる。以下の例を見てみよう(index.rb)。

#!/usr/koeki/bin/ruby

item = []  #=>itemは配列
number = 0

while true
  print "何か数字を入力してね(終了はend)\n"
  item[number] = gets.chomp!
  
  if item[number] == "end"
    then break
  end
  
  number += 1
end

p item

これを実行すると以下の通りとなる。

irsv{naoya}%ruby index.rb
何か数字を入力してね(終了はend)
100
何か数字を入力してね(終了はend)
200
何か数字を入力してね(終了はend)
300
何か数字を入力してね(終了はend)
end
["100","200","300","end"]

このプログラムではインデックスに具体的な値を指定するかわりに、item[number] = gets.chomp!というようにnumberという変数を指定している。プログラムの上部を見ると、numberの初期値は0である。よってwhile文で繰り返しを行うにあたり、1回目に入力された値はitem[0]に代入されることになる。そのままwhile文の中を見ていこう。下の方にnumber += 1があり、numberの値が1増加することになる。これによりwhile文の1回目の処理が終わり、2回目の処理を行う際はnumberは1であり、item[number] = gets.chomp!ではitem[1]に値が代入される。同様に、繰り返しを継続するたびにnumberの値が1増加するので、キーボードから入力された値はインデックスを1ずつ増加させながら配列内に順次代入されることになる。

参考までに、このプログラムではどこにもto_iが出現していない。このため配列内に代入された値を見ると"100"というように""が付いている。""がついているのは文字列であり、このままでは全てを足して合計を算出するということはできない。

また、配列の最後に"end"が値として代入されているという点も覚えておく必要がある。

インデックスを変数にして配列内の値を読み出す

配列に値を代入する際にインデックスに変数を指定したように、配列に代入されている値を表示する際にもインデックスを指定することができる。

例えば、item = [100,200,300]というように3つの商品の金額が代入されていたとする。ここで、3つの商品の金額を表示する場合、array.rbの書き方をそのままもってくれば

printf ("1個目%d円、2個目%d円、3個目%d円\n",item[0],item[1],item[2])

となる。既に気づいているだろうが、これもこのままでは使い勝手が悪い。商品を何個購入するかは既知ではないためである。ここでも配列のインデックスに変数を使用して、順番に値を読み出してみよう(index2.rb)。

#!/usr/koeki/bin/ruby

item = [100,200,300]
x = 0
sum = 0

while x < item.length
  printf ("%d個目     %d円\n",x+1,item[x])
  sum += item[x]
  x += 1
end
printf ("合計は%d円です\n",sum)

これを実行すると以下の通りとなる。

irsv{naoya}%ruby index2.rb
1個目     100円
2個目     200円
3個目     300円
合計は600円です

length:配列に対して、配列変数.lengthとすることでその配列の中に代入されている値の数を返してくれるメソッド。全く同じ働きをするメソッドとしてsizeもある。

このプログラムではインデックスにxという変数を使用し、xの初期値を0とし、while文の中でxの値を1ずつ増加させながらitem内の値を1つずつ順番に読み出している。ここでwhile文を継続する条件であるx < item.lengthについて見てみよう。lengthは上述の通り、配列内の値の数を返してくれるメソッドである。itemという配列には現在3つの値が代入されているため、item.lengthは3であり、これはx < 3と書いてあるのと等価である。xの初期値は0でありwhile文の中で1ずつ増加させているため、1回目はitem[0]、2回目はitem[1]、3回目はitem[2]が表示される。4回目になった時点でxは3になっており、x < 3という条件を満たさなくなるので、これで繰り返しが終了となり、最後にendの下にあるprintfの行が実行され、合計金額が表示される。

レポート課題

前回作成したregister.rbを以下の文章にしたがって改良しなさい。なお前回改良をして消費税率や金銭の授受ができるようにしたプログラムをベースとすればもっとも本格的なレジとなるが、課題の実施状況により、その前の段階のプログラムやwebページに掲載されているオリジナルのregister.rbをベースとして改良をしても良い。

最後に個々の商品の金額を表示した上で、合計金額を表示することができるように改良する。個々の商品の金額を表示する際は単に縦に並べるだけでも良いが、出来れば周囲に枠をつけ、本物のレシートらしくしてみること。

今回のプログラムの改良を行うにあたっては、index.rbとindex2.rbが参考になる。最後に個々の商品の金額を表示するためには、それぞれの金額を配列に代入しておく必要がある。この際、インデックスには数字を入れるのではなく変数を用いたほうが良い。index.rbではto_iがついていないため、入力された値が文字列として取り扱われていた。また、配列内の最後の値が"end"となっていた。このままでは合計が求められないので、このあたりの修正をいかに上手く行うかがポイントとなる。

レシート作成に関しては、以下の書き方を参考にすると良い。

  1. print "--------------------\n"とすることで、レシートの上部と下部の横線を引くことができる。
  2. printf ("| シナモノ    %d円 |\n",配列変数)のように、左右に|(パイプ)をおくことでレシートの左右の縦線を引くことができる。
  3. 今回は商品名まではわからないので、一括して「シナモノ」として良いが、余力があれば金額だけでなく商品名も入力し、これをレシートに表示させても良い。
  • 提出先:naoya@e.koeki-u.ac.jp
  • 提出期限:5/28(日)23:59(1限履修者)·6月4日(日)23:59(2限履修者)
  • メールのSubject:学籍番号-kadai04
  • 本文の構成:1行目で学籍番号、氏名を記載する。2行目以降は下記の構成とする
  1. 作成したプログラム
  2. プログラムの実行結果
  3. プログラムの説明
  4. 感想

  • 採点基準:期限内提出点(2点)、メールの体裁(1点)、プログラム(2点)、プログラムの説明(2点)、レシートの見栄え(1点)
  • プログラムの説明は、(1)配列とは何かについて自分の言葉で説明する、(2)index.rbやindex2.rbを利用するにあたり、自分なりに改良した点について説明する、の2点について重点的に記載すること。
  • 説明に関して、文章の意味がわかりづらい場合や、Webページを単にコピー&ペーストしたものは減点することがある。一度読み直してから提出すること。
  • 驚異的に良くできているレポートについては満点の8点を超える得点をつけることがある。
  • よくできていたレポートは、他の人の参考になるよう、本人が特定できないような形で掲載する。掲載してほしくない場合はメールでの課題提出時にその旨記載すること。

Tips:emacsでの日本語入力のオンオフはCtrl-oです

Tips:ktermでのプログラムの実行結果をメールに貼り付けるには、コピーしたい箇所をマウスで選択し、emacs(Mew)上でマウスの真ん中ボタンをクリックする

Tips:Mewによるメールの送り方はMewコマンドを参照