ハッシュは別名連想配列とも言われ、
任意の値に任意の値を結び付けることができる
データ型である。分かりやすい利用例としては、
ある文字列から連想する別の値を保存する
が挙げられる。たとえば、
といった情報を保存するときに、"富士山" という文字列に直接 3776を結び付けられるのがハッシュである。
ハッシュは、配列と同じ感覚で利用できる。配列の添字が整数に限られてい たのに対し、ハッシュはどんな種類の値でも添字に利用できる。たとえば、 つぎの値段表を変数に格納する場合を考えよう。
これまでの知識では、配列変数に格納する。
配列
変数 fruits | |||
[0] | [1] | [2] |
[3] |
"りんご" | "みかん" | "いちご" | "梨" |
変数 price | |||
[0] | [1] | [2] |
[3] |
150 | 30 | 20 | 120 |
2つの変数fruits, price
を用意し、
fruits
には品名を、price
には単価を
それぞれ配列として格納している。
ハッシュを利用すると、品名に対して直接価格を結び付けることができる。
ハッシュ
変数 price | |||||||||||||||||||||||
|
このような関係をRubyプログラムにしてみよう。ハッシュを 利用した簡単なプログラムは以下のようになる。
price = Hash.new(0) price["りんご"] = 150 price["みかん"] = 30 price["いちご"] = 20 price["梨"] = 120
各行の働きは以下のとおり。
price = Hash.new(0)
変数 price
に空っぽのハッシュを代入する。
Hash.new
が新しい空のハッシュ構造を作成することを意味
する。Hash.new(0)
とすると、ハッシュの値が登録されてい
ないときのデフォルト値を0にする(後述)。
price["りんご"] = 150
price
の持つハッシュの "りんご"
に
対応する値として 150
を代入している。配列の場
合、[]
の中に指定した整数のことを「添字」と呼んだが、
ハッシュの場合は key という。いっぽう、key に対して
結び付けられたもののことを value という。つまり、
key == "りんご" ↓ value == 150
となる。「key "りんご" の value は 150である」と表現する。
price["みかん"] = 30
price["いちご"] = 20
price["梨"] = 120
これらも、price
の持つハッシュのkey、
"みかん", "いちご", "梨"
それぞれに対し、valueとして
30, 20, 120
を代入している。
これらのハッシュの初期化は以下のように書いてもよい。
price = {"りんご" => 150, "みかん" => 30, "いちご" => 20, "梨" => 120}
このように、プログラムを書く段階でハッシュの初期値を入れる場合は
中括弧 {}
で括り、
「キー」 => 「値」
という組み合わせをカンマ(,)
で区切って列挙する。
price = { "りんご" => 150, "みかん" => 30, "いちご" => 20, "梨" => 120 }
のように見やすいように適当に改行を入れてもよい。
このようにして price
変数に入ったハッシュの値は、
配列変数と同じように []
で取り出すことができる。
price["みかん"] → 30 price["梨"] → 120
ハッシュの初期値を囲む括弧は中括弧 {}
だが、
ハッシュの値を取り出す時に使うのは大括弧 []
であることに
注意せよ。
実際にプログラムを動かしてみよう。
#!/usr/koeki/bin/ruby # coding: utf-8 price = Hash.new(0) price["りんご"] = 150 price["みかん"] = 30 price["いちご"] = 20 price["梨"] = 120 puts "りんご、みかん、いちご、梨、のどの値段を知りたいですか?" what = gets.chomp! cost = price[what] printf("%sは1個%d円です\n", what, cost) if cost == 0 printf("っていうか%sは売ってないんで...\n", what) end
上記プログラムを fruits.rb
という名前で保存し、
実行してみること。りんご、みかん、いちご、梨、を入れたり、
それ以外の名前を入れて何が返るか実験すること。
% ./fruits.rb りんご、みかん、いちご、梨、のどの値段を知りたいですか? りんご りんごは1個150円です
「りんご、みかん」などを入れるとき、 Shift+SPCで日本語をON/OFFして入力してもよいが、 マウスでコピー&ペーストすると楽である。 KTermに表示されたメッセージにマウスを合わせて左ボタンを ダブルクリックすると単語をコピーでき、真中ボタンをクリックすると ペーストできる。
ここまでのまとめ。
ハッシュの受け皿を作成するのは Hash.new
Hash.new(値)とすると、未定義のキーでアクセスした
場合のデフォルト値になる
あらかじめ値を持つハッシュをプログラム中に書くときは
{キー1 =>値1,キー2 =>値2,……
}
のように列挙する。
配列にデータを格納するときと同様、ハッシュに格納するときも最初からデー タの個数が決まっているわけではない。最初に空のハッシュを作成し、データを 入力するごとに値を格納していく。以下の例は県名と県庁所在地を順番に読み込 んでハッシュに格納するものである。
#!/usr/koeki/bin/ruby # coding: utf-8 pref = Hash.new # 新しいハッシュの生成 while true STDERR.print "都道府県名: " tdfk = gets if tdfk == nil break end tdfk.chomp! STDERR.print "都道府県庁所在地: " ofc = gets.chomp! pref[tdfk] = ofc end STDERR.print "読込終了\n" p pref # pを呼んでハッシュの値を全表示
実際に動かしてみよう。入力の終わりでは[C-d]をタイプする。
% ./pref.rb 都道府県名: 山形 都道府県庁所在地: 山形 都道府県名: 山梨 都道府県庁所在地: 甲府 都道府県名: 愛知 都道府県庁所在地: 名古屋 都道府県名: 神奈川 都道府県庁所在地: 横浜 都道府県名: 茨城 都道府県庁所在地: 水戸 都道府県名: [C-d]読込終了 {"愛知"=>"名古屋", "神奈川"=>"横浜", "山形"=>"山形", "茨城"=>"水戸", "山梨"=>"甲府"}
ハッシュには、key, value がペアで入っている。これを全て取り出すには
for
ループで2つの変数を使って順次取り出す。
たとえば、
yaoya = {"りんご" => 150, "みかん" => 30, "いちご" => 20, "梨" => 120}
と代入したとして、 keyとvalueを順に取り出す方法とその結果は以下のようになる。
for fruit, howmuch in yaoya do printf("当店の%sは%d円です\n", fruit, howmuch) end ↓ 当店のりんごは150円です 当店のいちごは20円です 当店のみかんは30円です 当店の梨は120円です
配列と違いハッシュでは、値を取り出すときの順番は入れたときの順番とは 無関係になる。
前期・情報検索の 配列で習った成績処理を思い出そう。まず、国語の試験の成績ファイルとして 以下のようなものを用意したとする(前期 第6講 のものと同じ)。
山田太郎 50 中町太郎 90 飯森花子 91 鶴岡一人 60 酒田三吉 52 三川一二三 12
これを集計するプログラムを思い出そう。ここで問題。
ファイルを1行ずつ読み込むループはどのように書くか?
名前と得点にマッチして、後で $1, $2
で
取り出せるような正規表現はどう書くか?
配列の 回と、そのときに自分が作ったプログラムをよく読み返して 上記の2つをしっかりと思い出そう
実際に作成したプログラムは以下のとおりである。
#!/usr/koeki/bin/ruby # coding: utf-8 score=[] name =[] n = 0 sum = 0 while yomikomi = gets if /(\S+)\s+(\d+)/ =~ yomikomi name[n], score[n] = $1, $2.to_i sum += score[n] n += 1 end end average = sum/n i = 0 print "--氏名--------------+-得点-+-平均との差--\n" while i < n printf("%-20s %5d %5.1f\n", name[i], score[i], score[i]-average) i += 1 end
このプログラムでは、氏名を保存する配列変数として
name[]
を、得点を保存する配列変数として
score[]
を利用した。また、このプログラムを拡張して、
数学の点も処理できるようにするためには、たとえばmath[]
という配列変数を用意する必要がある。このように、処理する対象
(ここでは科目数)が増えるたびに変数を増やさなければいけないのは効率がよく
ない。このようなときにはハッシュ構造を使うと
プログラムの見通しがよくなる。
ハッシュを用いて得点データを集計するプログラムを作ってみよう。 配列の回に作成した成績処理プログラムを ハッシュ構造を使って書き直してみよう。成績データをもう一度見よう。
山田太郎 50 中町太郎 90 飯森花子 91 鶴岡一人 60 酒田三吉 52 三川一二三 12
この構造は「りんご、みかん‥」の価格表と同じ構造にできることが分かる。 ハッシュにするなら、
変数 = Hash.new : 変数[氏名] = 得点 :
となるようにハッシュに代入して行けばよい。大元のプログラム
score.rb
では、
: while yomikomi = gets if /(\S+)\s+(\d+)/ =~ yomikomi name[n], score[n] = $1, $2.to_i :
の部分で、氏名と得点を抽出し、それぞれ name[], score[]
配列に入れている。ここをハッシュに変えよう。ハッシュに変えると
添字となる数値は必要なくなるので、以下のようになる(入力用の変数名も変えた)。
point = Hash.new : while yline = gets if /(\S+)\s+(\d+)/ =~ yline point[$1] = $2.to_i :
以上に注意して修正したのが以下のプログラムである。
#!/usr/koeki/bin/ruby # coding: utf-8 point = Hash.new sum = 0 while yline = gets if /(\S+)\s+(\d+)/ =~ yline point[$1] = $2.to_i sum += point[$1] end end average = sum.to_f/point.length print "--氏名--------------+-得点-+-平均との差--\n" for student, pt in point printf("%-20s %5d %5.1f\n", student, pt, pt-average) end
最後の、データを全て出力する部分
for student, pt in point 〜 end
では、氏名(key)と得点(value)を順次取り出している。 ただしハッシュから key/value ペアを取り出す場合の 順序は不同となるので注意すること。
実際にプログラム score-hash.rb
を実行して
確認しよう。
% ./score-hash.rb score.txt
--氏名--------------+-得点-+-平均との差--
中町太郎 90 30.8
山田太郎 50 -9.2
鶴岡一人 60 0.8
飯森花子 91 31.8
三川一二三 12 -47.2
酒田三吉 52 -7.2
実際のデータ(score.txt
)
山田太郎 50 中町太郎 90 飯森花子 91 鶴岡一人 60 酒田三吉 52 三川一二三 12
とは違う順に取り出されることに注意せよ。