ハッシュは別名連想配列とも言われ、任意の値に任意の値を結び付けることができるデータ型である。 分かりやすい利用例としては、辞書が挙げられる。たとえば、
このようなデータをRubyプログラムで表現するときに使われるのがハッシュである。
ハッシュは、配列と同じ感覚で利用できる。配列の添字が整数に限られていたのに対し、ハッシュはどんな種類の値でも添字に利用できる。 たとえば、つぎの値段表を変数に格納する場合を考えよう。
これまでの知識では、配列変数に格納する。
配列 fruits |
||||
インデックス | 0 | 1 | 2 | 3 |
値 | りんご | みかん | いちご | 梨 |
配列 prices |
||||
インデックス | 0 | 1 | 2 | 3 |
値 | 150 | 30 | 20 | 150 |
2つの変数(fruits, prices
)を用意し、fruits
には品名を、prices
には単価をそれぞれ配列として格納している。
ハッシュを利用すると、品名に対して直接価格を結び付けることができる。
ハッシュ fruit_prices |
||||
キー | りんご | みかん | いちご | 梨 |
値 | 150 | 30 | 20 | 150 |
このような関係をRubyプログラムにしてみよう。ハッシュを利用した簡単なプログラムは以下のようになる。
fruit_prices = Hash.new(0)
fruit_prices["りんご"] = 150
fruit_prices["みかん"] = 30
fruit_prices["いちご"] = 20
fruit_prices["梨"] = 150
各行の働きは以下のとおり。
fruit_prices = Hash.new(0)
変数 fruit_prices
に空っぽのハッシュを代入する。
Hash.new
が新しい空のハッシュ構造を作成することを意味する。
Hash.new(0)
とすると、
ハッシュの値が登録されていないときのデフォルト値を 0
にする(後述)。
fruit_prices["りんご"] = 150
fruit_prices
の持つハッシュの "りんご"
に対応する値として 150
を代入している。
配列の場合、[]
の中に指定した整数のことを「添字」または「インデックス」と呼んだが、
ハッシュの場合は「キー」(key)という。いっぽう、key に結び付けられた値のことを value
という。つまり、key "りんご" の value は 150
である。
fruit_prices["みかん"] = 30
fruit_prices["いちご"] = 20
fruit_prices["梨"] = 150
これらも、fruit_prices
の持つハッシュの key、
"みかん", "いちご", "梨"
それぞれに対し、value として
30, 20, 150
を代入している。
ハッシュの初期化は以下のように書くこともできる。
fruit_prices = {"りんご" => 150, "みかん" => 30,
"いちご" => 20, "梨" => 150}
このように、プログラムを書く段階でハッシュの初期値を入れる場合は
中括弧 { }
で括り、
「キー」 => 「値」
という組み合わせをカンマ (,
) で区切って列挙する。
fruit_prices = {
"りんご" => 150,
"みかん" => 30,
"いちご" => 20,
"梨" => 150
}
のように見やすいように適当に改行を入れてもよい。
このようにして fruit_prices
変数に入ったハッシュの値は、配列変数と同じように []
で取り出すことができる。
fruit_prices["みかん"] # => 30
fruit_prices["梨"] # => 150
ハッシュの初期値を囲む括弧は中括弧 {}
だが、
ハッシュの値を取り出す時に使うのは大括弧 []
であることに注意せよ。
実際にプログラムを動かしてみよう。
練習問題fruits.rb
#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-
fruit_prices = Hash.new(0)
fruit_prices["りんご"] = 150
fruit_prices["みかん"] = 30
fruit_prices["いちご"] = 20
fruit_prices["梨"] = 150
puts "りんご、みかん、いちご、梨、のどの値段を知りたいですか?"
product = gets.chomp
price = fruit_prices[product]
if price > 0
printf("%sは1個%d円です\n", product, price)
else
printf("%sは売っていません\n", product)
end
上記プログラムを fruits.rb
という名前で保存し、実行してみること。りんご、みかん、いちご、梨、を入れたり、それ以外の名前を入れて何が返るか実験すること。
% ./fruits.rb
りんご、みかん、いちご、梨、のどの値段を知りたいですか?
りんご
りんごは1個150円です
ハッシュの key は重複できない。以下のように、同一の key で複数回代入を行うと、最後に代入をした key と value のペアの値が保持される。
練習問題 (黄色で書かれたコードをirb
で入力し実行すること)
irb(main):001:0> fruit_prices = Hash.new(0)
irb(main):002:0> fruit_prices["みかん"] = 30
irb(main):003:0> fruit_prices["みかん"] = 40
irb(main):004:0> fruit_prices["みかん"]
=> 40
ここまでのまとめ。
空のハッシュを作成するには Hash.new
とする。
Hash.new(値)
とすると、未定義のキーでアクセスした場合のデフォルト値になる。デフォルト値を設定しないと、値が入っていないキーを参照した場合には nil
が返される。
あらかじめ値を持つハッシュをプログラム中に書くときは {キー1 => 値1,キー2 => 値2,……}
のように列挙する。
配列にデータを格納するときと同様、ハッシュに格納するときも最初からデータの個数が決まっているわけではない。 最初に空のハッシュを作成し、データを入力するごとに値を格納していく。 以下の例は国名と首都を順番に読み込んでハッシュに格納するものである。
練習問題capitals.rb
#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-
capitals = Hash.new # 新しいハッシュの生成
while true
print "国名(読込終了は q): "
country = gets.chomp
if country == "q"
break
end
print "首都: "
cap_city = gets.chomp
capitals[country] = cap_city
end
print "読込終了\n"
p capitals # pを呼んでハッシュの値を全表示
実際に動かしてみよう。入力の終わりでは q
をタイプする。
% ./capitals.rb
国名(読込終了は q): 日本
首都: 東京
国名(読込終了は q): ポーランド
首都: ワルシャワ
国名(読込終了は q): カナダ
首都: オタワ
国名(読込終了は q): ジョージア
首都: トビリシ
国名(読込終了は q): スリランカ
首都: スリ・ジャヤワルダナプラ・コッテ
国名(読込終了は q): q
読込終了
{"日本"=>"東京", "ポーランド"=>"ワルシャワ", "カナダ"=>"オタワ",
"ジョージア"=>"トビリシ", "スリランカ"=>"スリ・ジャヤワルダナプラ・コッテ"}
最初に示した果物の価格の例ではプログラム中に埋め込んでいた価格ハッシュをCSVファイルから読み取るようにする。読み取るデータファイルは以下の
fruits.csv
とする。
りんご,150
みかん,30
いちご,20
梨,150
プログラム:
練習問題fruits_from_file.rb
#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-
fruit_prices = Hash.new(0)
open("fruits.csv", "r") do |f|
while line = f.gets
if /(.*),(\d+)/ =~ line then
#$1=品名 $2=価格 (いずれも最初は文字列)
fruit_prices[$1] = $2.to_i # 価格を整数化
end
end
end
puts "りんご、みかん、いちご、梨、のどの値段を知りたいですか?"
product = gets.chomp
price = fruit_prices[product]
if price > 0
printf("%sは1個%d円です\n", product, price)
else
printf("%sは売っていません\n", product)
end
ハッシュには、key, value がペアで入っている。これを全て取り出すには
for
ループで2つの変数を使って順次取り出す。
たとえば、
練習問題all_fruit_prices.rb
#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-
fruit_prices = {"りんご" => 150, "みかん" => 30,
"いちご" => 20, "梨" => 150}
for fruit, price in fruit_prices do
printf("当店の%sは%d円です\n", fruit, price)
end
結果は以下のようになる。
当店のりんごは150円です
当店のいちごは20円です
当店のみかんは30円です
当店の梨は150円です
ハッシュに格納されているキーだけを取り出すには keys
メソッドを利用する。たとえば上記の果物リストのある fruit_prices
変数の値から、キーだけを取り出すと以下のようになる。
fruit_prices.keys
=> ["りんご", "みかん", "いちご", "梨"]
これを利用して、ハッシュに備わっているキーの一覧を表示したい場合は以下のようにすればよい。
練習問題fruit-keys.rb
#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-
fruit_prices = {"りんご" => 150, "みかん" => 30, "いちご" => 20, "梨" => 150}
fruit_prices.keys.each do |key| # または: for key in fruit_prices.keys
puts(key)
end
print("以上のうちどの値段を調べますか: ")
fruit = gets.chomp
if fruit_prices[fruit] then
printf("%sは%d円です。\n", fruit, fruit_prices[fruit])
else # デフォルト値設定なしなので見つからなければ nil
printf("残念ながら「%s」は取り扱っていません。\n", fruit)
end
以下のような実行結果となる。
% ./fruit-keys.rb
りんご
みかん
いちご
梨
以上のうちどの値段を調べますか: いちご
いちごは20円です。
% ./fruit-keys.rb
りんご
みかん
いちご
梨
以上のうちどの値段を調べますか: 柿
残念ながら「柿」は取り扱っていません。
実用的なプログラムでは、実行時にデータベースなどからハッシュを取得するので、キーをプログラム中に列挙したりせず、ハッシュそのものから取り出すことになる。
irb
で入力し実行すること)
ハッシュの初期宣言として
irb(main):001:0> x = Hash.new
のように、デフォルト値を指定しなかった場合、存在しない key の value を参照すると nil
が返る。
irb(main):002:0> x["hoge"] # "hoge" というkeyは未登録 => nil
ところがもし、
irb(main):003:0> x = Hash.new(0)
のように、デフォルト値を指定した場合、存在しない key の value を参照すると、指定した値が返る。
irb(main):004:0> x["hero"] # "hero" というkeyは未登録 => 0
初期宣言として、Hash.new()
を利用した場合はデフォルト値が指定できるが、{ }
を利用してハッシュの初期値を代入した場合は、デフォルト値は nil
となる。つまり、
irb(main):005:1* x = { irb(main):006:1* "りんご" => "apple", irb(main):007:1* "みかん" => "orange", irb(main):008:1* "いちご" => "strawberry" irb(main):009:0> }
と初期値代入した場合、未登録keyのvalueを参照すると nil
となる。
irb(main):010:0> x["梨"] # "梨" というkeyは未登録 => nil
このようなときに、あとからデフォルト値を設定するには ハッシュに属するメソッドである default=
を利用する。
irb(main):011:0> x.default="わかりませーん"
すると、以下のようになる。
irb(main):012:0> x["梨"] # "梨" というkeyは未登録 => "わかりませーん" irb(main):013:0> x["りんご"] # "りんご" というkeyは登録されている => "apple"
ちなみに配列の場合も、決まった長さ(要素数)のものを、決められた値で埋めつくした初期配列を作ることができる。これには Array.new
を利用する。
Array.new(長さ)
Array.new(長さ, 初期値)
指定した 長さ の要素数を持つ配列を作成する。 初期値 を指定した場合には、全ての要素にその初期値を代入する。
irb
で入力し実行すること)
たとえば、
irb(main):001:0> Array.new(5, 0)
とすると、
[0, 0, 0, 0, 0]
という配列が作成される。初期値を指定せず、
irb(main):002:0> Array.new(4)
などとした場合は、
[nil, nil, nil, nil]
のように、各要素の初期値が nil
の配列が作成される。
ハッシュと違い、配列には存在しない範囲の添字の値を参照したときのデフォルト値は指定できない。範囲外の添字を指定したときの値は
nil
となる。
irb(main):003:0> arr = Array.new(5, 0) => [0, 0, 0, 0, 0] irb(main):004:0> arr[10] => nil
ハッシュを使って、和英辞典プログラム dictionary.rb
を作成せよ。
プログラムを起動すると、以下の選択肢が表示される:
1 - 単語を検索
2 - 新しい単語を登録
0 - 終了
ユーザーが0を入力しない限り、何度でも1・2を選べるように、無限ループとbreakを使うこと。
辞書データは dictionary.txt というファイルで保管すること。
アリ ant
猫 cat
犬 dog
象 elephant
ナマケモノ sloth
スズメ sparrow
ユーザーが検索したい単語が辞書に登録されていない場合はメッセージを表示すること。また、ハッシュの key は重複できないので追加しようとする単語がすでに辞書にある場合にも、「この単語はすでに登録済みです」というメッセージを表示し新規登録を中止すること。
実行結果の例:
{c11xxxx}% ruby dictionary.rb
OPTIONS:
1 - 単語を検索
2 - 新しい単語を登録
0 - 終了
1
検索したい単語を入力してください
象
象 は elephant です
OPTIONS:
1 - 単語を検索
2 - 新しい単語を登録
0 - 終了
1
検索したい単語を入力してください
熊
この単語は辞書に登録されていません
OPTIONS:
1 - 単語を検索
2 - 新しい単語を登録
0 - 終了
2
日本語の単語を入力してください
熊
英語の単語を入力してください
bear
単語が登録されました
OPTIONS:
1 - 単語を検索
2 - 新しい単語を登録
0 - 終了
1
検索したい単語を入力してください
熊
熊 は bear です
OPTIONS:
1 - 単語を検索
2 - 新しい単語を登録
0 - 終了
2
日本語の単語を入力してください
熊
この単語は既に登録済みです
OPTIONS:
1 - 単語を検索
2 - 新しい単語を登録
0 - 終了
0
発展課題