(4) 10/30の授業内容:配列の復習とハッシュ

配列の復習への導入

5回の課題の平均点を計算するケースについて考えてみる。ユーザが1回目から5回目の得点を順次入力し、その値に基づき平均点を計算する。最も単純なプログラムでは以下のように書けるであろう(sample1.rb)。

#!/usr/koeki/bin/ruby

print "1回目の得点を入力してください:"
score1 = gets.chomp!.to_f
print "2回目の得点を入力してください:"
score2 = gets.chomp!.to_f
print "3回目の得点を入力してください:"
score3 = gets.chomp!.to_f
print "4回目の得点を入力してください:"
score4 = gets.chomp!.to_f
print "5回目の得点を入力してください:"
score5 = gets.chomp!.to_f

sum = score1 + score2 + score3 + score4+ score5
average = sum / 5

printf ("平均点は%5.2f点です\n",average)

平均点を求めるという目的を達成する上では、この書き方で十分である。しかし、このプログラムでは同じような内容が繰り返し登場している。これをもう少しシンプルに書くことができないだろうか。このプログラムは既に学んでいる繰り返し表現を用いることで簡潔な記述が可能となる(sample2.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)

while文を用いることで、printによるメッセージの表示とgetsメソッドによる入力データの受取がそれぞれ1行で済んでいることがわかる。もしもテストの回数を5回から10回に変更した場合、sample1.rbではprintとgetsの行をそれぞれ5行追加する必要があるが、sample2.rbではtimes=5をtimes=10に変更するだけでよい。なお、これらのプログラムを実行すると、実行結果はどちらの場合も同じで以下のようになる。ここで黄色の部分がユーザーが入力した値である。

irsv{学籍番号}% ruby sample2.rb
1回目の得点を入力してください:30
2回目の得点を入力してください:40
3回目の得点を入力してください:50
4回目の得点を入力してください:60
5回目の得点を入力してください:70
平均点は50.00点です。

while文を用いたプログラムでは、設定を変更する場合の容易さを指摘できる反面で、若干の問題もある。このプログラムではユーザーが入力した個別の得点を記憶しておくことができない。sample1.rbでは各回の得点はscore1やscore2などの異なる変数に代入されるため、全ての値をあとで呼び出すことも可能である。しかし、sample2.rbでは入力をした各回の得点は全て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個の値が代入されている。最大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]となる。[]の中の数字のことを添字もしくはインデックスと呼ぶ。

sample2.rbを配列を用いて書き直してみよう(sample3.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 = []とし、配列である旨宣言をしている。そして、sample2.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]に順番に代入されることになる。

このプログラムを実行すると、結果はsample2.rbと変わらないが、sample3.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)

ハッシュとは何か

配列ではインデックスを使用して値を順次代入したり、取り出したりすることができた。ハッシュも配列と似たような形態をとるが、ハッシュではインデックスのかわりにkeyを用いる。keyは通し番号ではなく、任意の値を取ることができる。以下は左側が配列の例、右側がハッシュの例である。

menu = Hash.new
menu["カレーライス"]=600
menu["ハンバーグ"]=800
menu["ロールキャベツ"]=750
menu["牛丼"]=400
menu["ラーメン"]=500
menu["田舎煮定食"]=650
menu = []
menu[0]=600
menu[1]=800
menu[2]=750
menu[3]=400
menu[4]=500
menu[5]=650

これはメニューと金額を示したものである。ハッシュの場合、keyを任意の値とすることができるため、カレーライスやハンバーグをキーとして、対応する金額を代入することができる。keyに対応する金額をvalueと呼ぶ。配列ではインデックスは通し番号となるため、金額のみが代入される形となり何の値段なのかを対応づけることができない。配列を用いて対応付けを考える場合、menuという配列以外に、例えばpriceという配列を設け、menu[0]にカレーライスを代入し、price[0]に600を代入するという作業が必要となる。

まずはハッシュの特徴と用法についてまとめてみよう。

  1. ハッシュ:任意の値に任意の値を結びつけて保存をすることができる変数。
  2. menu = Hash.new:menuがハッシュ変数であることを宣言。
  3. menu["カレーライス"]=600:menuの持つハッシュの"カレーライス"に対応する値として600を代入している。配列のインデックスに相当するものをハッシュではkey(キー)と呼び、keyに対して結び付けられたデータをvalue(バリュー)と呼ぶ。

ハッシュへの初期値の与え方

以下のデータをハッシュの初期値として与えておくことを考えよう。

チーズラーメン

600

納豆ラーメン

700

キムチラーメン

780

まぐろラーメン

800

牛乳ラーメン

500

配列の場合は、gakushoku=[350,350,250,250,120,450]というように両側を[]でくくり、中のデータはカンマを入れて記述したのに対し、ハッシュではkey => valueというように、keyとvalueのペアを並べて表記する。ペアとペアの区切りにはカンマをつける。また、配列とは異なり両側を{}で囲んで示す

noodle = {"チーズラーメン" => 600, "納豆ラーメン" => 700, "キムチラーメン" => 780, "まぐろラーメン" => 800, "牛乳ラーメン" => 500}

記述をする際には、下記のように適宜改行を入れて見やすく書いてもよい。

noodle = {
         "チーズラーメン" => 600,
         "納豆ラーメン" => 700,
         "キムチラーメン" => 780,
         "まぐろラーメン" => 800,
         "牛乳ラーメン" => 500
         }

ハッシュへの代入

ハッシュへの代入を行う場合、keyとvalueをペアで指定する必要がある。ハッシュの初期値を記載する場合は{}を用いたが、ハッシュへの代入および、ハッシュからの値の取り出しは[]を使用する

実際のプログラムを見ながら確認してみよう(weather.rb)。

#!/usr/koeki/bin/ruby

place = Hash.new(0)
place["酒田"] = "晴後曇"
place["新庄"] = "晴れ"
place["山形"] = "曇り"
place["米沢"] = "晴時々曇"

STDERR.print "どこの天気が知りたいですか(酒田、新庄、山形、米沢から選んでください):"
what = gets.chomp!
weather = place[what]

if weather == 0
  printf ("%sのデータはありません\n", what)
else
  printf ("%sの天気は%sです\n", what, weather)
end

以下に実行例を示す。ktermでの日本語入力のON/OFFはShift+SPACEである(シフトを押しながらスペースキーを押す)。

irsv{学籍番号}% ruby weather.rb
どこの天気が知りたいですか(酒田、新庄、山形、米沢から選んでください):米沢
米沢の天気は晴時々曇です

実行結果の例では、ユーザは米沢と入力している。入力データの受け取りは

what = gets.chomp!

の行で行われており、whatに"米沢"が代入される。そして

weather = place[what]

の行ではplaceというハッシュ変数において、whatをkeyとするvalueを調べ、これをweatherに代入している。これによりweatherには米沢に対応するvalueである晴時々曇が代入されることになる。

なお、冒頭でplace = Hash.new(0)として、placeがハッシュ変数であることを宣言しているが、Hash.newのかわりにHash.new(0)としている。Hash.new(値)にすると、定義されていないkeyが代入された場合に、この値が返されるようになる。

このプログラムでは、冒頭でplaceがハッシュ変数であることを宣言し、その後でkeyとvalueをペアで代入している。ハッシュに初期値を与える場合にはハッシュ変数の宣言は不要になる。ただし、このプログラムでも最初から代入するkeyとvalueのペアが固定されており、実質的に初期値を与えているようにもとらえることができ、初期値を与える場合は{}を使うという意味がわかりづらい。そこで、次はkeyとvalueをユーザが自由に入力してハッシュに代入をするケースをみてみよう。

出席課題

weather.rbにおいてplace = Hash.new(0)の(0)を取り除いてplace = Hash.newとした場合に、未定義のkeyを入力するとどのようになるだろうか。実行して確かめてみよう。

制限時間は10分。出席点は2点。提出要領は下記の通り。

  • 提出先:naoya@e.koeki-u.ac.jp
  • メールのSubject:ruby04
  • 本文の構成:1行目で学籍番号、氏名を記載する。2行目以降に実行結果を貼り付け、何が発生したのかを簡潔に説明する。

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

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

ハッシュへの代入(その2)

ハッシュに何らかのデータを代入する場合、keyとvalueのいずれも与える必要がある。以下はgetsメソッドを使ってユーザの入力を受け取りハッシュに代入していくプログラムである。

#!/usr/koeki/bin/ruby

ball = Hash.new	# 新しいハッシュの生成
while true
  print "親会社(登録終了の場合は おしまい と入力): "
  company = gets.chomp!
  if company == "おしまい"
    break
  end
  print "チーム名: "
  team = gets.chomp!
  ball[company] = team
end
print "読込終了\n"

$KCODE = 'e'	# 漢字コードをEUCに指定

p ball.keys
p ball.values
p ball

このプログラムでは、野球チームの親会社とチーム名を入力しハッシュに代入している。まず冒頭でball = Hash.newと宣言し、ballをハッシュ変数としている。そして、親会社とチーム名について入力を求め、それぞれcompanyとteamに代入している。その後で、

ball[company] = team

の行で、companyをkey、teamをvalueとしてballに代入している。以下が実行結果である。

% ruby baseball.rb
親会社(登録終了の場合は おしまい と入力):西武
チーム名:ライオンズ
親会社(登録終了の場合は おしまい と入力):ソフトバンク
チーム名:ホークス
親会社(登録終了の場合は おしまい と入力):阪神
チーム名:タイガース
親会社(登録終了の場合は おしまい と入力):おしまい
["西武","ソフトバンク","阪神"]
["ライオンズ","ホークス","タイガース"]
{"西武"=>"ライオンズ", "ソフトバンク"=>"ホークス", "阪神"=>"タイガース"}

このプログラムでは最後にpメソッドを使ってハッシュに代入された値を表示している。ここで、

p ball.keys

とするとkeyのみが表示される。

p ball.values

とするとvalueのみが表示される。

p ball

とするとkey=>valueの形式で表示される。

なお、pメソッドの上の行に、$KCODE = 'e'とある。pメソッドが表示しようとする変数内の値に日本語文字列が含まれる場合、そのまま表示すると文字化けをおこしてしまう。これを防ぐために、$KCODE = 'e'を上の行に挿入し、日本語文字コードがEUCであると指定している。

ハッシュから値を取り出す

ハッシュから値を取り出す方法を、配列と比較しながら見ていこう。いずれもレジでレシートを作成するプログラムを想定している。購入した商品と金額はバーコードにより読取り済みであり、配列もしくはハッシュに代入済みとする。

配列を使用した場合は下記の通りとなる(receipt-array.rb)。商品名が代入された配列itemと、金額が代入された配列kingakuという2つの配列変数を使用している。配列からの値の取出しにはインデックスを使用するため、初期値を0とするiという変数を使用し、while文で繰り返し表示をしている。

#!/usr/koeki/bin/ruby

item = ["牛乳","醤油","玉葱","納豆","林檎","牛肉",]
kingaku = [98,298,198,128,300,698]
i = 0
sum = 0

while i < kingaku.length
  sum += kingaku[i]
  printf ("%s\t%d円\n",item[i],kingaku[i])
  i += 1
end

printf ("合計\t%d円\n",sum)

ハッシュを使用した場合は下記の通りとなる(receipt-hash.rb)。ハッシュの場合、インデックスではなくkeyとvalueを用いているため、iの値を1ずつ増やしながら繰り返し表示をするwhileが使用できない。ここではfor文を使用している。shina、moneyにはそれぞれkey、valueの値が代入される。

#!/usr/koeki/bin/ruby

kingaku = { "牛乳" => 98,
            "醤油" => 298,
            "玉葱" => 198,
            "納豆" => 128,
            "林檎" => 300,
            "牛肉" => 698
            }
i = 0
sum = 0

for shina, money in kingaku
  printf ("%-10s\t%5d円\n",shina, money)
  sum += money
end

printf ("合計\t%d円\n",sum)

for文について若干説明する。

for shina, money in kingaku

の行で、ハッシュ変数のkingakuのkeyとvalueを対で取り出し、それぞれshinaとmoneyに代入している。そして、printfの行とsum+=moneyの行を実施する。kingakuのkeyとvalueを対で1つずつ取り出しながら全てのデータを読み出すまでfor-end間を繰り返し実施する。

なお、receipt-hash.rbを実行した結果は下記の通りである。ハッシュの場合、代入した順番と結果の順番が必ずしも一致しないので、おかしな順番であっても間違っているわけではない。

irsv{学籍番号}% ruby receipt-hash.rb
牛乳	98円
牛肉	698円
林檎	300円
納豆	128円
玉葱	198円
醤油	298円
合計	1720円

レポート課題

以下のうちいずれかを選んで解答する。

問題1(8点満点):授業中に取り上げたweather.rbを参考に、自分の好きなデータをハッシュに代入しておき、あとから検索できるプログラムを作成する。検索できるデータは、誕生月と誕生石、山と標高、Rubyメソッドとその働き、都道府県と県庁所在地など好きなものを選ぶこと。他の人とは違うもの、楽しいものが望ましい。最低でもデータは10個作ること。

問題2(10点満点):あなたはA社の社員である。取引先のB社とメールで連絡を取ろうと考えているが、ライバルのC社にメールを傍受される恐れがあることが判明した。取引を完遂するために、C社に情報が漏れることは許されない。そこで、以下のような変換テーブルを用いた暗号化通信を行うことにした。このテーブルをA社とB社で共有することで、重要情報も通信過程では無意味な情報とすることができる(ちなみに双方で変換テーブルを共有する暗号化方式を共通鍵暗号方式という)。伝達しなければならない情報は4桁の自然数のコードである。A社からB社に連絡することもあれば、B社からA社に連絡が来ることもある。これを踏まえて以下の条件を満たすプログラムDES.rbを作成せよ。

  • 暗号化モードと復号モードを持ち、実行時にどちらのモードを適用するかを指定することができる。例えば暗号化モードでは4桁の数字[1234]は[4720]となる。一方復号モードでは[1234]は[7391]となる。
  • 入力データは常に4桁の数字であり、4桁を1度に入力するものとする。つまり4桁を1桁ずつ入力し、1桁ずつ変換を行って最後に合体するという方法はNGとする。

変換前

変換後

0

6

1

4

2

7

3

2

4

0

5

9

6

5

7

1

8

8

9

3


  • 提出先:naoya@e.koeki-u.ac.jp
  • 提出期限:11/05(日)23:59
  • メールのSubject:自分の学籍番号-kadai03
  • 本文の構成:1行目で学籍番号、氏名を記載する。2行目以降は下記の構成とする
  1. 作成したプログラム
  2. プログラムの実行結果
  3. プログラムの説明
  4. 感想

  • 採点基準(問題1):期限内提出点(2点)、メールの体裁(1点)、プログラム(2点)、独自性(1点)、プログラムの説明(2点)
  • 採点基準(問題2):期限内提出点(2点)、メールの体裁(1点)、プログラム(4点)、プログラムの説明(3点)
  • プログラムの説明(問題1):使用したデータと、ハッシュへのデータの代入方法および取り出し方法について説明すること。
  • 独自性(問題1):今回は自分で自由に作成するプログラムであるため、プログラムや説明が他の人と偶然に一致する可能性は低いと考えられる。ほかの人と同じプログラムの場合は0点(変数名だけ違う場合も0点)、ほかに同じプログラムを書いた人がいない場合は1点とする。
  • プログラムの説明(問題2):4桁の数字を一度に与えて1桁ずつ変換をするための工夫、暗号化の方法、復号の方法について説明すること
  • 説明に関して、文章の意味がわかりづらい場合や、Webページを単にコピー&ペーストしたものは減点することがある。一度読み直してから提出すること。
  • 驚異的に良くできているレポートについては満点を超える得点をつけることがある。
  • よくできていたレポートは、他の人の参考になるよう、本人が特定できないような形で掲載する。掲載してほしくない場合はメールでの課題提出時にその旨記載すること。

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

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

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