ハッシュ

ハッシュとは

ハッシュは別名連想配列とも言われ、

任意の値に任意の値を結び付けることができる

データ型である。分かりやすい利用例としては、

ある文字列から連想する別の値を保存する

が挙げられる。たとえば、

といった情報を保存するときに、"富士山" という文字列に直接 3776を結び付けられるのがハッシュである。

ハッシュの特性

ハッシュは、配列と同じ感覚で利用できる。配列の添字が整数に限られてい たのに対し、ハッシュはどんな種類の値でも添字に利用できる。たとえば、 つぎの値段表を変数に格納する場合を考えよう。

  1. りんご・・・150円
  2. みかん・・・ 30円
  3. いちご・・・ 20円
  4. 梨・・・・・120円

これまでの知識では、配列変数に格納する。

配列

変数 fruits
[0][1][2] [3]
"りんご""みかん""いちご""梨"
変数 price
[0][1][2] [3]
1503020120

2つの変数fruits, priceを用意し、 fruitsには品名を、priceには単価を それぞれ配列として格納している。

ハッシュを利用すると、品名に対して直接価格を結び付けることができる。

ハッシュ

変数 price
"りんご"
150

"梨"
120
"いちご"
20
"みかん"
30

このような関係をRubyプログラムにしてみよう。ハッシュを 利用した簡単なプログラムは以下のようになる。

price = Hash.new(0)
price["りんご"] = 150
price["みかん"] = 30
price["いちご"] = 20
price["梨"]  = 120

各行の働きは以下のとおり。

これらのハッシュの初期化は以下のように書いてもよい。

price = {"りんご" => 150, "みかん" => 30,
         "いちご" => 20, "梨" => 120}

このように、プログラムを書く段階でハッシュの初期値を入れる場合は 中括弧 {} で括り、

「キー」 => 「値」

という組み合わせをカンマ(,) で区切って列挙する。

price = {
  "りんご" => 150,
  "みかん" => 30,
  "いちご" => 20,
  "梨" => 120
}

のように見やすいように適当に改行を入れてもよい。

このようにして price 変数に入ったハッシュの値は、 配列変数と同じように [] で取り出すことができる。

price["みかん"]
  → 30
price["梨"]
  → 120

ハッシュの初期値を囲む括弧は中括弧 {} だが、 ハッシュの値を取り出す時に使うのは大括弧 [] であることに 注意せよ。

実際にプログラムを動かしてみよう。

fruits.rb

#!/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に表示されたメッセージにマウスを合わせて左ボタンを ダブルクリックすると単語をコピーでき、真中ボタンをクリックすると ペーストできる。

ここまでのまとめ。

ハッシュへの値の追加手順

配列にデータを格納するときと同様、ハッシュに格納するときも最初からデー タの個数が決まっているわけではない。最初に空のハッシュを作成し、データを 入力するごとに値を格納していく。以下の例は県名と県庁所在地を順番に読み込 んでハッシュに格納するものである。

pref.rb

#!/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講 のものと同じ)。

score.txt

山田太郎        50
中町太郎        90
飯森花子        91
鶴岡一人        60
酒田三吉        52
三川一二三      12

これを集計するプログラムを思い出そう。ここで問題。

  1. ファイルを1行ずつ読み込むループはどのように書くか?

  2. 名前と得点にマッチして、後で $1, $2 で 取り出せるような正規表現はどう書くか?

配列の 回と、そのときに自分が作ったプログラムをよく読み返して 上記の2つをしっかりと思い出そう

実際に作成したプログラムは以下のとおりである。

score.rb

#!/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
 :

以上に注意して修正したのが以下のプログラムである。

score-hash.rb

#!/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

とは違う順に取り出されることに注意せよ。


本日の目次