6. ハッシュ

ハッシュ(Hash)

ハッシュとは

ハッシュは、配列と同様にオブジェクトの集まりを表現するためのオブジェクトです。
配列がインデックス(数値)を添字として利用するのに対して、ハッシュは文字列やシンボルなど様々なオブジェクトを添字として使用できます。
ハッシュを利用するには{}(ブレース)を用いて次のように記述します。

1
{ key0 => elem0, key1 => elem1, key2 => elem2, ... }

以下にフルーツ名をハッシュで記述し、参照する例を示します。

print_hash.rb
1
2
3
4
5
6
7
8
# フルーツをハッシュで保持する
fruits = { "banana" => "バナナ", "mango" => "マンゴー", "lemon" => "レモン" }

p fruits["banana"]  # キーがbananaの要素を出力する
#=> バナナ

p fruits            # ハッシュの全体を出力する
#=> {"banana" => "バナナ", "mango" => "マンゴー", "lemon" => "レモン"}

ハッシュの利点は連想配列であることです。
つまり、キー(key)の名称から要素(value)が何を表す値かを連想することができ、より直感的に要素を扱うことができます。
例えば、テストの点数を例に配列とハッシュを比較してみましょう。

exam_scores.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 配列の場合
scores = [65, 80, 62, 88]    # 国語、数学、英語、情報の順番で要素を入れる

# 国語と情報の点数を表示する
p scores[0]    # 国語は配列の1番目(添字0)の要素
#=> 65

p scores[3]    # 情報は配列の4番目(添字3)の要素  <= 本当に合ってる?ぱっと見て分からない…
#=> 88

# ハッシュの場合
scores_hash = { "japanese" => 65, "math" => 80, "english" => 62,  "information_technology" => 88 }

# 国語と情報の点数を表示する
p scores_hash["japanese"] # 国語はキーが"japanese"の要素
#=> 65

p scores_hash["math"]     # 数学はキーが"math"の要素  <= キーが何の要素を表すか分かりやすい!
#=> 88

上記のように、配列でよりもハッシュのほうが「何の値を扱っているのか」が理解しやすくなります。
ただし、キーは一意でなければなりません。キーを同じ値にしてしまうと、前の要素は上書きされるので注意しましょう。

シンボルをキーとして扱う

キーにはどんなオブジェクトも設定できますが、読み書きのしやすさからシンボルがよく用いられています。
混乱を避けるため、研修中はできるだけシンボルを使用するようにしましょう。

1
fruits = { :apple => 150,:banana => 200,:lemon => 300 }

また、Ruby1.9以降から、キーがシンボルの場合に限り、以下のような記述ができるようになりました。
最近のソースコードはこの書き方が多いので、注意してください。

1
fruits = { apple: 150, banana: 200, lemon: 300 } # 作成されるオブジェクトは上記と同じ

要素の参照・変更・追加

要素の参照や変更、追加は以下の通りに行います。

hash_operations.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
fruits = { :apple => 150, :banana => 200, :lemon => 300 }

# 要素の参照
p fruits[:apple]
#=> 150

p fruits[:banana]
#=> 200

p fruits[:papaya]
#=> nil

# 要素の変更
fruits[:apple] = 1000
p fruits[:apple]
#=> 1000

# 要素の追加
fruits[:papaya] = 2000
p fruits
#=>{:apple => 1000, :banana => 200, :lemon => 300, :papaya => 2000}

要素の変更と追加は同じ記述となります。
ハッシュオブジェクトに存在しないキーを指定して値を代入した場合は新しい要素としてハッシュに追加されます。

ハッシュの繰り返し(each・each_with_index)

配列と同様、ハッシュも繰り返し構文eachが使用できます。
ブロックに「キー」と「要素」をそれぞれ渡すところが配列と異なります。

hash_each.rb
1
2
3
4
5
6
7
8
9
fruits = { :apple => 150, :banana => 200, :lemon => 300 }

fruits.each do |name, price|
  puts "#{name}の値段は#{price}円です"
end

#=> appleの値段は150円です
#=> bananaの値段は200円です
#=> lemonの値段は300円です

インデックス情報もブロック内に渡すeach_with_indexも使用できます。
ただし、eachと同様にブロックに渡す引数が異なるので注意しましょう。

hash_each_with_index.rb
1
2
3
4
5
6
7
8
9
children = { :ichiro => 7, :jiro => 4, :saburo => 2 }

children.each_with_index do |(name, age), i|
  puts "#{i+1}番目のお子さん:#{name}さんは#{age}歳です"
end

#=> 1番目のお子さん:ichiroさんは7歳です
#=> 2番目のお子さん:jiroさんは4歳です
#=> 3番目のお子さん:saburoさんは2歳です

また、ハッシュも配列と同様に{}を用いた表現方法があります。
こちらも配列同様、研修中は一行で表現したい場合のみ、{}を用いるようにしましょう。

1
hash.each { |key, value| puts "#{key}#{value}です" }

Note : each_key / each_value

ハッシュには「キーのみ」、または「要素のみ」をブロックに渡すeach文があります。
使用する機会はあまりないかもしれませんが覚えておきましょう。

each_key.rb
1
2
3
4
5
6
7
8
9
fruits = { :apple => 150, :banana => 200, :lemon => 300 }

fruits.each_key do |name|
  p name
end

#=> apple
#=> banana
#=> lemon
each_value.rb
1
2
3
4
5
6
7
8
9
10
fruits = { :apple => 150, :banana => 200, :lemon => 300 }

total_price = 0

fruits.each_value do |price|
  total_price += price
end

puts total_price
#=> 650

ハッシュのソート(sort・sort_by)

配列と同様に、ハッシュにもsortメソッドやsort_byメソッドが使用できます。
ただし、結果が配列として返って来ることに注意しましょう。
特に条件を指定せずにsortメソッドを実行した場合、「キー」を基準として昇順にソートします。

hash_sort.rb
1
2
3
4
5
scores = { :Carol => 90, :Alice => 50, :Bob => 60, :David => 40 }

# ハッシュをキーでソート
p scores.sort
# => [[:Alice, 50], [:Bob, 60], [:Carol, 90], [:David, 40]]

また、sortにブロックを渡すことで、キーだけでなく値などでもソートすることができます。
このとき、ブロックの第一引数と第二引数にはそれぞれ配列[key, value]の組が渡されることに注意してください(例:[:Carol, 90])。
以下の例は、ブロックを用いて「ハッシュの値」(点数)で昇順にソートする例です。

hash_sort_with_block.rb
1
2
3
4
5
6
7
8
9
10
11
12
scores = { :Carol => 90,:Alice => 50,:Bob => 60,:David => 40 }

# ハッシュを値でソート
# key_and_value1, key_and_value2には、それぞれ[key, value]の組からなる配列が渡される
sorted_scores = scores.sort { |key_and_value1, key_and_value2| key_and_value1[1] <=> key_and_value2[1] }
p sorted_scores
# #=> [[:David, 40], [:Alice, 50], [:Bob, 60], [:Carol, 90]]

# 最初からキーと値を展開することもできる
sorted_scores = scores.sort { |(key1, value1), (key2, value2)| value1 <=> value2 }
p sorted_scores
# #=> [[:David, 40], [:Alice, 50], [:Bob, 60], [:Carol, 90]]

ハッシュに対してsort_byメソッドを用いると、任意の基準に応じたソートをすることができます。
ブロックの引数にはそれぞれ第一引数にキー、第二引数に値が渡されます。

hash_sort_by.rb
1
2
3
4
5
6
7
8
9
10
11
scores = { :Carol => 90, :Alice => 50, :Bob => 60, :David => 40 }

# ハッシュを値でソート
sorted_scores = scores.sort_by { |name, score| score }
p sorted_scores
#=> [[:David, 40], [:Alice, 50], [:Bob, 60], [:Carol, 90]]

# ハッシュをキーの文字列の長さでソート
sorted_scores = scores.sort_by { |name, score| name.length }
p sorted_scores
#=> [[:Bob, 60], [:Carol, 90], [:Alice, 50], [:David, 40]]

演習問題

問1

次の問に従って、自分の属性をハッシュで表現してみましょう。

問1-1

以下のような属性を持つハッシュ{{自分の名前}}_statusを作成し、:colorの中身を出力してください。

キー(シンボル)
:name(名前) "駅助"
:color(テーマカラー) "赤"
:employee_num(社員番号) 472
1
#=> 赤

問1-2

{{自分の名前}}_statusの定義(問1-1)は変えず、プログラムから以下の属性を追加し、ハッシュの中身を"キー : 値"の形式で全て出力してください。

キー(シンボル)
:tools ["ふせん", "ペン", "社員証"]
1
2
3
#=> name : 駅助
#=> color : 赤
#=> tools : ["ふせん", "ペン", "社員証"]

問1-3

以下のような属性を持つハッシュmembersを作成し、membersを使って自分のテーマカラーを出力してください。

キー(シンボル)
:{{自分の名前}} 問1-2で得られたハッシュ
1
#=> 赤

問4(オプション)

問3で作成したmembersに同期や架空のメンバーをハッシュとして追加し、任意の値を出力してみましょう。