ハッシュの利用

ハッシュを利用するためには、色々な構造のデータをハッシュ形式にうまく 保存していく方法と、あとでハッシュから key と value を取り出すための方法に慣れておく必要がある。いくつかの題材を 見ながら、ハッシュを効率的に利用するメソッドとその使い方を覚えていこう。

少し複雑なハッシュ

成績処理のデータで、国語以外の点を追加しよう。データは以下のとおりとする。

score2.txt

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

ハッシュは、このような表形式のものを構造的に格納することに向いている。 どの行も、「氏名」、「国語得点」、「数学得点」の順に並んでいる。 表計算ソフト等での入力イメージが以下のようなものだとする。

namejamath
山田太郎5040
中町太郎9070
飯森花子9190
鶴岡一人6050
酒田三吉5280
三川一二三1275

このような場合、各行の一件の値の集合(レコードという)をそれぞれハッシュで表す。

name jamath
山田太郎5040
name jamath
中町太郎9070
name jamath
飯森花子9190
name jamath
鶴岡一人6050
name jamath
酒田三吉5280
name jamath
三川一二三1275

これらの各ハッシュを配列に格納する。 上記の例の場合は、以下のような「6個のハッシュを要素に持つ配列」 として表現できる。

[{"name"=>"飯森花子", "ja"=>91, "math"=>90},
 {"name"=>"中町太郎", "ja"=>90, "math"=>70},
 {"name"=>"鶴岡一人", "ja"=>60, "math"=>50},
 {"name"=>"山田太郎", "ja"=>50, "math"=>40},
 {"name"=>"酒田三吉", "ja"=>52, "math"=>60},
 {"name"=>"三川一二三", "ja"=>12, "math"=>75}
]

このような構造で値を格納するプログラムは以下のようになる。

score-hash2.rb

#!/usr/koeki/bin/ruby
# coding: utf-8
require "./kprintf.rb"

point = Array.new	# 個別ハッシュを格納する全体配列
sum = Array.new(2,0)	# sumは配列にする。要素2個、各初期値は0
ave = Array.new		# aveも配列にする。

while yline = gets
  if /(\S+)\s+(\d+)\s+(\d+)/ =~ yline
    # 1個目の() (\S+)→氏名が入る
    # 2個目の() (\d+)→国語の得点が入る
    # 3個目の() (\d+)→数学の得点が入る
    name, ja, math = $1, $2.to_i, $3.to_i
    point << {"name" => name, "ja"=>ja, "math"=>math}
    sum[0] += ja	# 国語
    sum[1] += math	# 数学
  end
end
ave[0] = sum[0].to_f/point.length # 国語の平均点
ave[1] = sum[1].to_f/point.length # 数学の平均点

print "--氏名--------------+-国語-+-平均との差--+-数学-+-平均との差--\n"
for i in point
  # i には、"name", "ja", "math" をキーとするハッシュが入って来る
  student = i["name"]	# iのキー"name"に対応する値が氏名
  kokugo  = i["ja"]	# iのキー"ja"に対応する値が国語の点
  math    = i["math"]	# iのキー"math"に対応する値が数学の点
  printf("%-20s %5d   %5.1f       %5d    %5.1f\n", student,
    kokugo, kokugo-ave[0],
    math, math-ave[1])
end
puts "-"*62
printf("%-20s %5.1f%s%5.1f\n", "平均点", ave[0], " "*15, ave[1])

このように、1単位のデータ(レコード)をハッシュとして表現し、 それを配列などに複数集めて処理することはしばしば用いられる。

ハッシュの初期宣言

ハッシュの初期宣言として

x = Hash.new

のように、デフォルト値を指定しなかった場合、存在しない key の value を参照するとnilが返る。

x["hoge"]      # "hoge" というkeyは未登録
 => nil

ところがもし、

x = Hash.new(0)

のように、デフォルト値を指定した場合、存在しない key の value を参照すると、指定した値が返る。

x["hero"]      # "hero" というkeyは未登録
 => 0

初期宣言として、Hash.new() を利用した場合は デフォルト値が指定できるが、{ } を利用して ハッシュの初期値を代入した場合は、デフォルト値は nil となる。つまり、

x = {
  "りんご" => "apple", "みかん" => "orange",
  "いちご" => "strawberry"
}

と初期値代入した場合、未登録keyのvalueを参照するとnil となる。

x["梨"]      # "梨" というkeyは未登録
 => nil

このようなときに、あとからデフォルト値を設定するには ハッシュに属する メソッドである default= を利用する。

x.default="わかりませーん"

すると、以下のようになる。

x["梨"]      # "梨" というkeyは未登録
 => "わかりませーん"
x["りんご"]  # "りんご" というkeyは登録されている
 => "apple"

配列の場合は、決まった長さ(要素数)のものを、決められた値で 埋めつくした初期配列を作ることができる。これには Array.new を利用する。

たとえば、

Array.new(5, 0)

とすると、

[0, 0, 0, 0, 0]

という配列が作成される。初期値を指定せず、

Array.new(4)

などとした場合は、

[nil, nil, nil, nil]

のように、各要素の初期値が nil の配列が作成される。 ハッシュと違い、配列には存在しない範囲の添字の値を参照したときの デフォルト値は指定できない。範囲外の添字を指定したときの値は nil となる。


本日の目次