roy > naoya > 基礎プログラミングII > (3)ハッシュ[1]
(3) 10/18の授業内容:ハッシュ[1]
[1]ハッシュとは何か
配列ではインデックスを使用して値を順次代入したり、取り出したりすることができた。ハッシュも配列と似たような形態をとるが、ハッシュではインデックスのかわりにkeyを用いる。keyは通し番号ではなく、任意の値を取ることができる。以下は左側が配列の例、右側がハッシュの例である。
menu = Hash.new menu["カレーライス"]=600 menu["ハンバーグ"]=800 menu["ロールキャベツ"]=750 menu["牛丼"]=400 menu["ラーメン"]=500 menu["田舎煮定食"]=650
price = [] price[0]=600 price[1]=800 price[2]=750 price[3]=400 price[4]=500 price[5]=650
これはメニューと金額を示したものである。ハッシュの場合、keyを任意の値とすることができるため、カレーライスやハンバーグといった文字列をkeyとして、対応する金額を代入している。keyに対応する金額をvalueと呼ぶ。配列ではインデックスは通し番号となるため、金額のみが代入される。このままではメニューとの対応が取れないため、priceという配列以外に、例えばmenuという配列を設け、menu[0]にカレーライスを代入し、price[0]に600を代入するという作業が必要となる。
まずはハッシュの特徴と用法についてまとめてみよう。
ハッシュの特徴と用法
- ハッシュ:任意の値に任意の値を結びつけて保存をすることができる変数。
- menu = Hash.new:menuがハッシュ変数であることを宣言。
- menu["カレーライス"]=600:menuの持つハッシュの"カレーライス"に対応する値として600を代入している。配列のインデックスに相当するものをハッシュではkey(キー)と呼び、keyに対して結び付けられたデータをvalue(バリュー)と呼ぶ。
[2]ハッシュへの初期値の与え方
以下のデータをハッシュの初期値として与えることを考えよう。
key | → | value |
---|---|---|
チーズラーメン | → | 600 |
納豆ラーメン | → | 700 |
キムチラーメン | → | 780 |
まぐろラーメン | → | 800 |
牛乳ラーメン | → | 500 |
配列の場合は、price=[350,350,250,250,120,450]というように両側を[]でくくり、中のデータはカンマを入れて記述したのに対し、ハッシュではkey => valueというように、keyとvalueのペアを並べて表記する。ペアとペアの区切りにはカンマをつける。また、配列とは異なり両側を{}で囲んで示す。=>は=(イコール)と>(不等号)を組み合わせる。
ハッシュの初期値の与え方(その1)
noodle = {"チーズラーメン" => 600, "納豆ラーメン" => 700, "キムチラーメン" => 780, "まぐろラーメン" => 800, "牛乳ラーメン" => 500}
keyのチーズラーメンや納豆ラーメンは文字列であるため""をつけている。valueは数字であるため""はついていない。ちなみにkeyが数字の場合は""は不要であるし、valueが文字列であれば""をつける必要がある。なお記述をする際には、以下のように適宜改行を入れて見やすく書いてもよい。
ハッシュの初期値の与え方(その2)-改行を入れて見やすくする
noodle = { "チーズラーメン" => 600, "納豆ラーメン" => 700, "キムチラーメン" => 780, "まぐろラーメン" => 800, "牛乳ラーメン" => 500 }
[3]ハッシュへの代入
ハッシュへは、keyとvalueをペアで代入する。ハッシュの初期値の記載には{}を用いたが、ハッシュへの代入には[]を使用する(team.rb)。
#!/usr/koeki/bin/ruby
ball = Hash.new # 新しいハッシュの生成
while true
print"親会社(登録終了は q): "
company = gets.chomp!
if company == "q"
break
end
print"チーム名: "
team = gets.chomp!
ball[company] = team
end
$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に代入している。以下が実行結果である。
pan{c10xxxx}% ruby team.rb[Return] 親会社(登録終了は q):スイート[Return] チーム名:メイプルス[Return] 親会社(登録終了は q):公益[Return] チーム名:サンダース[Return] 親会社(登録終了は q):サンタナ[Return] チーム名:カーズ[Return] 親会社(登録終了は q):q[Return] ["スイート","公益","サンタナ"] ["メイプルス","サンダース","カーズ"] {"スイート"=>"メイプルス", "公益"=>"サンダース", "サンタナ"=>"カーズ"}
このプログラムでは最後にpメソッドを使ってハッシュに代入された値を表示している。ここで、
p ball.keys
とするとkeyのみが表示される。
p ball.values
とするとvalueのみが表示される。
p ball
とするとkey=>valueの形式で表示される。
なお、pメソッドの上の行に、$KCODE = 'e'とある。pメソッドが表示しようとする変数内の値に日本語文字列が含まれる場合、そのまま表示すると文字化けをおこしてしまう。これを防ぐために、$KCODE = 'e'を上の行に挿入し、日本語文字コードがEUCであると指定している。
ハッシュへの代入方法
- 冒頭でhoge=Hash.newとする。
- その後、hoge[key]=valueの形で、keyとvalueをペアにしてhogeに代入する。
[4]ハッシュ内の値の取り出し(1) keyとvalueをペアで取り出す
ハッシュに代入された値を取り出す方法を、配列と比較しながら見ていこう。いずれもレジでレシートを作成するプログラムを想定している。購入した商品と金額はバーコードにより読取り済みであり、配列もしくはハッシュに代入されているものとする。
配列の場合
配列を使用したプログラムを示す(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)
実行するとインデックス0から順番に表示される。
pan{c10xxxx}% ruby receipt-hash.rb[Return] 牛乳 98円 醤油 298円 玉葱 198円 納豆 128円 林檎 300円 牛肉 698円 合計 1720円
ハッシュの場合
ハッシュを使用した場合は以下の通りとなる(receipt-hash.rb)。ハッシュの場合、インデックスではなくkeyとvalueを用いているため、iの値を1ずつ増やしながら繰り返し表示をするwhile-endが使用できない。ここではfor-endを使用している。item、moneyにはそれぞれkey、valueの値が代入される。
#!/usr/koeki/bin/ruby
kingaku = { "牛乳" => 98,
"醤油" => 298,
"玉葱" => 198,
"納豆" => 128,
"林檎" => 300,
"牛肉" => 698
}
sum = 0
for item, money in kingaku #keyをitem、valueをmoneyに代入
printf("%-10s\t%5d円\n",item, money)
sum += money
end
printf("合計\t%d円\n",sum)
for文について若干説明する。
for item, money in kingaku
の行で、ハッシュ変数のkingakuのkeyとvalueを対で取り出し、それぞれitemとmoneyに代入している。そして、printfの行とsum+=moneyの行を実施する。kingakuのkeyとvalueを対で1つずつ取り出しながら全てのデータを読み出すまでfor-end間を繰り返し実施する。
receipt-hash.rbの実行結果は以下の通りである。ハッシュの場合、代入した順番と結果の順番が必ずしも一致しないことがわかる。
pan{c10xxxx}% ruby receipt-hash.rb[Return] 牛乳 98円 牛肉 698円 林檎 300円 納豆 128円 玉葱 198円 醤油 298円 合計 1720円
ハッシュへからの値の取り出し方法
- hogeをハッシュ変数とする場合、for foo, bar in hogeというfor文を用いる。hogeのkeyをfoo、valueをbarに代入し、繰り返し処理を行う。
- 配列とは異なりハッシュでは代入した順番と取り出された順番が異なる。
[5]ハッシュ内の値の取り出し(2) keyを指定してvalueを取り出す
ハッシュに代入されているkeyを指定してvalueを取り出す方法について見てみよう。まずは実際のプログラムを見ながら確認しよう(capital.rb)。
#!/usr/koeki/bin/ruby
place = Hash.new(0)
place["エリトリア"] = "アスマラ"
place["セントルシア"] = "カストリーズ"
place["ツバル"] = "フナフティ"
place["モンテネグロ"] = "ポドゴリツァ"
STDERR.print "どこの国の首都が知りたいですか\n
(エリトリア、セントルシア、ツバル、モンテネグロ):"
where = gets.chomp!
capital = place[where]
if capital == 0
printf("%sのデータはありません\n", where)
else
printf("%sの首都は%sです\n", where, capital)
end
このプログラムでは、hoge[key]=valueの形式でハッシュへの代入を行っているが、{}を用いて初期値という形で代入をしても良い。以下に実行例を示す。ktermでの日本語入力のON/OFFはCtrl+oである。
pan{c10xxxx}% ruby capital.rb[Return] どこの国の首都が知りたいですか (エリトリア、セントルシア、ツバル、モンテネグロ):エリトリア エリトリアの首都はアスマラです
実行結果の例では、ユーザはエリトリアと入力している。入力データの受け取りは
where = gets.chomp!
の行で行われており、whereに"エリトリア"が代入される。そして
capital = place[where]
の行ではplaceというハッシュ変数において、whereをkeyとするvalueを調べ、これをcapitalに代入している。これによりcapitalにはエリトリアに対応するvalueであるアスマラが代入されることになる。
冒頭のplace = Hash.new(0)はplaceが空っぽのハッシュ変数であることを宣言するものであるが、Hash.newのかわりにHash.new(0)としている。Hash.new(値)にすると、定義されていないkeyが指定された場合に、valueとしてこの値が返される。
keyを指定したvalueの取り出し
- hoge = piyo[key]でハッシュ変数piyoにおけるkeyに対応するvalueをhogeに代入する。
- 冒頭でpiyo=Hash.new(値)とすると、valueを取り出すにあたり存在しないkeyを指定した場合にこの値が返される。
[6]出席課題
capital.rbにおいてplace = Hash.new(0)の(0)を取り除いてplace = Hash.newとした場合に、未定義のkeyを入力するとどのようになるだろうか。実行して確かめてみよう。
その上で、Hash.new(0)の(0)を取り除いてplace = Hash.newとしても、「○○のデータはありません」というメッセージが出るようにプログラムを書き換えてみよう。
制限時間は10分。出席点は2点。提出要領は下記の通り。
- 提出先:課題提出用メールアドレス
- メールのSubject:attend03
- 本文の構成:1行目で学籍番号、氏名を記載する。2行目以降に実行結果を貼り付け、何が発生したのかを簡潔に説明する。追加の問題も解いた場合は、プログラムの変更箇所を示す。
Tips:emacsでの日本語入力のオンオフはCtrl+oです
Tips:Mewによるメールの送り方はMewコマンドを参照
[7]ハッシュのvalueを配列にする
ハッシュはkeyとvalueを使い、任意の値に任意の値を結びつけ、ペアでデータを保存することができるものであった。では、次のようなデータ(name.txt)をハッシュ変数に代入する場合はどうしたらよいだろうか。
氏 名 年齢 血液型 山田一郎 21 A 佐藤二郎 22 B 加藤三郎 23 B 藤田四郎 24 O 木村五郎 25 A 鈴木六郎 26 O
これをハッシュに代入しようとすると戸惑うことになる。key「山田一郎」のvalueが「21とA」という2つの値になっているためである。
実はこのような場合でもハッシュへの代入は可能である。keyとvalueは1対1の対応をしなければならないというわけではなく、1つのkeyに対して複数のvalueを結びつけることができる。つまりvalueを配列にすることができる。上記の例は次のように書くことができる。
name["山田一郎"] = [21, "A"]
Aは文字列なので""をつけており、21は数字なので""をつけていない。name.txtを読み込んでハッシュに代入し、結果を表示するプログラムを書くと以下の通りとなる(onamae.rb)。
#!/usr/koeki/bin/ruby
raw = Hash.new
while line = gets
if /(\S+)\s+(\d+)\s+(\S+)/ =~ line
# 1個目の() (\S+)→氏名が入る
# 2個目の() (\d+)→年齢が入る
# 3個目の() (\S+)→血液型が入る
raw[$1] = [$2, $3] # 配列を代入
end
end
for name, data in raw
# data には、[年齢, 血液型] という配列が入っている
printf("%-10sさんは%2d歳で、血液型は%s型です。\n",
name, data[0], data[1])
# dataの第0要素が年齢、第1要素が血液型
end
pan{c10xxxx}% ruby onamae.rb name.txt[Return] 鈴木六郎 さんは26歳で、血液型はO型です。 木村五郎 さんは25歳で、血液型はA型です。 藤田四郎 さんは24歳で、血液型はO型です。 加藤三郎 さんは23歳で、血液型はB型です。 佐藤二郎 さんは22歳で、血液型はB型です。 山田一郎 さんは21歳で、血液型はA型です。
結果を表示するにあたり、for-endの構文を用いてkeyとvalueをペアで取り出し、それぞれnameとdataに代入している。dataについては、今回はvalueが年齢と血液型という2つの値をもつ配列であるため、printfではdata[0],data[1]というようにインデックスを指定することで、それぞれの値を取り出せるように指示している。
なお配列とは異なり、ハッシュでは値を取り出すときの順番は、読み込んだ順番とは無関係になる。ハッシュはmd5(Message Digest 5)という方法でデータを暗号化して格納する。暗号化されたデータの大きさによって取り出される順番が決まるため、読み込んだ順番と異なることになる。
参考までにこのプログラムを配列で書くと次のようになる(onamae-array.rb)。
#!/usr/koeki/bin/ruby
name = [] #氏名用の配列
age = [] #年齢用の配列
blood = [] #血液型用の配列
i = j = 0 #配列のインデックス
while line = gets
if /(\S+)\s+(\d+)\s+(\S+)/ =~ line
# 1個目の() (\S+)→氏名が入る
# 2個目の() (\d+)→年齢が入る
# 3個目の() (\S+)→血液型が入る
name[i] = $1
age[i] = $2
blood[i] = $3
i += 1
end
end
while j < i
printf("%-10sさんは%2d歳で、血液型は%s型です。\n",
name[j], age[j], blood[j])
j += 1
end
[8]ハッシュ利用上の注意
ハッシュ変数を利用する上で、知っておいた方がよい事柄についてまとめる。
keyは重複できない
#!/usr/koeki/bin/ruby
menu = Hash.new
menu["うどん"] = 300
menu["うどん"] = 400
$KCODE = 'e'
p menu #=>{"うどん"=>400}
同一のkeyで複数回代入を行うと、最後に代入をしたkeyとvalueのペアの値が保持される。
valueは重複可
#!/usr/koeki/bin/ruby
menu = Hash.new
menu["うどん"] = 350
menu["ラーメン"] = 350
$KCODE = 'e'
p menu #=>{"うどん"=>350, "ラーメン"=>350}
同一のvalueがあってもkeyが異なっていれば代入可能。
keyやvalueの有無は調査可能
ハッシュ.key?(値)、ハッシュ.value?(値)でそれぞれkeyやvalueの有無を調べることができる。その値がkeyやvalueとして代入されていればTrueを返し、代入されていなければFalseを返す。
#!/usr/koeki/bin/ruby
menu = Hash.new
menu["うどん"] = 350
menu["ラーメン"] = 350
$KCODE = 'e'
p menu.key?("うどん") #=>true
p menu.key?("そば") #=>false
[9]レポート課題
好きな店舗の名前を考えなさい(商標権を侵害しないように!)。次にその店舗が取り扱う商品を10個以上考え、それぞれについて金額の設定をしなさい。その上で以下についてできるところまで実施しなさい(ruby2-2.rb)。
- capital.rbを参考に、keyに商品名、valueに金額を代入しておき、商品名と購入数を入力すると合計金額が表示されるようにする
- 取り扱っていない商品名を入力した場合には、取り扱っていない旨を表示する
- プログラム起動〜終了の間に、複数種類の商品を購入できるようにする
- valueを配列にして金額と在庫数を代入しておき、在庫数を上回る販売ができないようにする
- 在庫数の変化を次回プログラム起動時に持ち越す
- プログラム終了時に在庫数がn個を下回る商品については発注を行い、次回起動時には在庫をx個増加させる。nとxは自由に定めてよい。単にプログラム終了時にx個増加させておくだけで良いが、発注から納品のプロセスが可視化されていればなお良い(つまり、次回起動時にx個納品されたことが通知されるようにする)
- 提出先:課題提出用メールアドレス
- 提出期限:10/24(日)23:00
- メールのSubject:課題2
- 本文の構成:1行目で学籍番号、氏名を記載する。2行目以降は下記の構成とする
- 店舗名・取扱商品
- 何番まで実施したか
- 作成したプログラム
- プログラムの実行結果
- プログラムの説明
- 感想
- 採点基準:期限内提出点(2点)、メールの体裁(1点)、プログラム(1個0.5点)、プログラムの説明(2点)
- プログラムの説明:1〜6のプログラムの改良点について、それぞれ説明する。対応関係がわかるようにすること。
- わかりにくい説明や、Webページを単にコピー&ペーストしただけの説明は減点することがある。一度読み直してから提出すること。
- 驚異的に良くできているレポートについては満点を超える得点をつけることがある。
- よくできていたレポートは、他の人の参考になるよう、本人が特定できないような形で掲載する。掲載してほしくない場合はメールでの課題提出時にその旨記載すること。
Tips:emacsでの日本語入力のオンオフはCtrl+oです
Tips:ktermでのプログラムの実行結果をメールに貼り付けるには、コピーしたい箇所をマウスで選択し、emacs(Mew)上でマウスの真ん中ボタンをクリックする
Tips:Mewによるメールの送り方はMewコマンドを参照