こんにちは!じゃいごテックのあつしです。
今回はハッシュ(連想配列)の基本操作についてご紹介します。
ハッシュ(連想配列)は複数の値をまとめて格納できるオブジェクトで、それぞれの要素はキーのオブジェクトと値がペアになっており、キーを与えることによって要素の値にアクセスすることが出来ます。構造が配列とよく似ていて、共通で使えるメソッドも沢山あります。
ハッシュを生成する
要素を直接入力して生成する
{ キー => 値 }
{} 波カッコ、又は Hash.new で空のハッシュを生成できます。また、キー => 値 で要素を作成し、各要素を , カンマで区切ることで複数の要素を格納することが出来ます。
キーには文字列や数値、シンボルなど(※)を使うことができますが、シンボルを使った方が高速に処理が出来ます。
※ キーには任意のオブジェクト(例えばリストなど)を用いることができ、話を広げていくと説明しきれなくなってしまうので、本記事では「文字列のキー」「シンボルのキー」で説明します。
# 空のハッシュを生成
hash1 = {}
p hash1
# > {}
hash2 = Hash.new
p hash2
# > {}
# 文字列のキーと値でハッシュを生成
hash3 = {"apple" => 200, "orange" => 300, "banana" => 150}
p hash3
# > {"apple"=>200, "orange"=>300, "banana"=>150}
# シンボルのキーと値でハッシュを生成
hash4 = {apple: 200, orange: 300, banana: 150}
p hash4
# > {:apple=>200, :orange=>300, :banana=>150}
# シンボルのキーはこのようにも書ける
hash5 = {:apple => 200, :orange => 300, :banana => 150}
p hash5
# > {:apple=>200, :orange=>300, :banana=>150}
to_hメソッド
[[キー, 値], [キー, 値], [キー, 値] ... ].to_h
キーと値がペアとなっている配列をハッシュに変換します。キーと値がペアになっていない場合はエラーとなります
# キーと値がペアになっている配列をハッシュに変換
ary1 = [["yamada", 75], ["suzuki", 88], ["sasaki", 60]]
p ary1.to_h
# > {"yamada"=>75, "suzuki"=>88, "sasaki"=>60}
# キーと値がペアになっていない要素がある場合
ary2 = [["yamada", 75], ["suzuki", 88], ["sasaki", 60, "AB"]]
p ary2.to_h
# > ArgumentError (wrong array length at 2 (expected 2, was 3))
ary3 = [["yamada", 75], ["suzuki", 88], ["sasaki"]]
p ary3.to_h
# > ArgumentError (wrong array length at 2 (expected 2, was 1))
to_hメソッドを使ってpaiza練習問題を解いてみる
価格の算出 (paizaランク C 相当)
※ paiza レベルアップ問題集 データセット選択メニューより
問題:
paiza 商店には N 個の商品が売られており、i 番目の商品の名前は A_i で価格は B_i です。あなたはほしい M 個の商品名のリスト S を持っています。それぞれ paiza 商店ではいくらで売られているか答えてください。売られていない場合は -1 を出力してください。
入力される値
N M A1 B1 A2 B2 ... A_N B_N S1 S2 ... S_M
期待する出力
商品 S_i の値段 T_i を答えてください。paiza 商店に商品 S_i が売られていない場合は - 1 を出力してください。
T1 T2 T3 ... T_M
条件
すべてのテストケースにおいて、以下の条件をみたします。
- N, M, B_i は 1 以上 100 以下
- S, A の各文字列は英小文字からなる
- S, A の各文字列は 1 文字以上 10 文字以下
- S, A の各文字列に重複はありません
入力例1
3 4
eraser 50
pencil 30
book 100
book
eraser
pencil
margaret
出力例1
100
50
30
-1
解答例:
# [解答例1]
# 商品数 N と 購入希望数 M を受け取る
N, M = gets.split.map(&:to_i)
# 続く N 行で 商品名 A と 価格 B を受けとりハッシュに追加していく
# (A -> name, B -> price とする)
item_list = Hash.new
N.times do
name, price = gets.split
item_list[name] = price.to_i
end
# -------------------------------------------
# 入力例1 の場合 item_list の中身は...
# p item_list
# > {"eraser"=>50, "pencil"=>30, "book"=>100}
# -------------------------------------------
# 続く M 行で希望商品の価格を検索する
M.times do
price = item_list[gets.chomp]
if price
# 希望商品があれば価格表示
puts price
else
# 希望商品がなければ(nil なら) -1 表示
puts -1
end
end
# -------------------------------------------
# item_list はこうなっているので...
# item_list = { "eraser"=>50,
# "pencil"=>30,
# "book"=>100 }
#
# 続くM=4 行の入力に対する処理
# 入力1: "book"
# item_list["book"] には 100 が格納されているので 100 と表示
# 入力2: "eraser"
# item_list["eraser"] には 50 が格納されているので 50 と表示
# 入力3: "eraser"
# item_list["pencil"] には 30 が格納されているので 30 と表示
# 入力4: "margaret"
# item_list["margaret"] は定義されておらず nil が返るので -1 と表示
# -------------------------------------------
# [解答例2]
# 入力を一括で受け取る
tmp_lines = STDIN.read.split("\n")
# 商品数 N と 購入希望数 M を受け取る
N, M = tmp_lines.shift.split.map(&:to_i)
# 先頭から N 行分の商品データを受け取ってハッシュに変換する
item_list = tmp_lines.shift(N).map(&:split).map{|key, val| [key, val.to_i]}.to_h
# -------------------------------------------
# 入力例1 の場合 item_list の中身は...
# p item_list
# > {"eraser"=>50, "pencil"=>30, "book"=>100}
#
# tmp_lines の中身は...
# p tmp_lines
# ["book", "eraser", "pencil", "margaret"]
# -------------------------------------------
# 希望商品の価格を検索する
# 希望商品が item_list にあれば価格を出力、ない (nil) なら -1 を出力
tmp_lines.each{ |name| puts item_list[name] || -1 }
# -------------------------------------------
# item_list はこうなっているので...
# item_list = { "eraser"=>50,
# "pencil"=>30,
# "book"=>100 }
#
# 続く M 行の入力文字列
# tmp_lines = ["book", "eraser", "pencil", "margaret"]
#
# 入力1: "book"
# item_list["book"] には 100 が格納されているので 100 と表示. (100 || -1) -> 100
# 入力2: "eraser"
# item_list["eraser"] には 50 が格納されているので 50 と表示. (50 || -1) -> 50
# 入力3: "eraser"
# item_list["pencil"] には 30 が格納されているので 30 と表示. (30 || -1) -> 30
# 入力4: "margaret"
# item_list["margaret"] は 定義されておらず nil が返るので -1 と表示. (nil || -1) -> -1
# -------------------------------------------
group_byメソッド
配列.group_by{ |変数| 評価 }
配列やハッシュにブロックで条件を与えて、評価した結果をキー、対応する要素の配列が値となるハッシュを返します。
種類毎の集計を行うのに便利です。
ary = ["A", "A", "B", "C", "C", "D", "E", "C", "B", "C", "D"]
# 種類毎にグループ化したハッシュを生成する
p ary.group_by{ |element| element }
# > {"A"=>["A", "A"], "B"=>["B", "B"], "C"=>["C", "C", "C", "C"], "D"=>["D", "D"], "E"=>["E"]}
# こう書いても同じ
p ary.group_by(&:itself)
# > {"A"=>["A", "A"], "B"=>["B", "B"], "C"=>["C", "C", "C", "C"], "D"=>["D", "D"], "E"=>["E"]}
# 配列の値をグループ化したハッシュを生成し、値に要素数を入れる
p ary.group_by(&:itself).map{ |key, val| [key, val.length] }.to_h
# > {"A"=>2, "B"=>2, "C"=>4, "D"=>2, "E"=>1}
tallyメソッド
配列.tally
配列(Enumerableオブジェクト)の各要素を数え上げた結果を集計して、ハッシュで返します。
ary = ["A", "A", "B", "C", "C", "D", "E", "C", "B", "C", "D"]
# 種類毎に集計したハッシュを返す
p ary.tally
# > {"A"=>2, "B"=>2, "C"=>4, "D"=>2, "E"=>1}
group_byメソッドを使ってpaiza練習問題を解いてみる
要素の種類数 (paizaランク D 相当)
※ paiza レベルアップ問題集 配列活用メニューより
問題:
配列 A の要素数 N と配列 A の各要素 A_1, A_2, ..., A_N が与えられるので、配列 A には何種類の値が含まれているかを求めてください。
入力される値
N A_1 ... A_N
期待する出力
配列 A に含まれている値の種類数を 1 行で出力してください。
条件
- 1 ≦ N ≦ 100
- 0 ≦ A_i ≦ 100 (1 ≦ i ≦ N)
入力例1
1
1
出力例1
1
入力例2
5
1
2
3
2
1
出力例2
3
解答例:
# [解答例1]
# 要素数 N を受け取る
N = gets.to_i
# 続く N 行で 配列 A を受け取る(A -> ary とする)
ary = N.times.map { gets.to_i }
# ary の要素の種類を集計してハッシュに変換する
summary = ary.group_by(&:itself)
# 集計結果 summary の要素数(種類数)を出力する
puts summary.length
# [解答例2]
# 入力を一括で受け取る
_, *ary = STDIN.read.split("\n").map(&:to_i)
# ary の要素を集計してハッシュに変換する
summary = ary.group_by(&:itself)
# 集計結果 summary の要素数(種類数)を出力する
puts summary.length
値を取得する
ハッシュ["キー"]
[] 角カッコでキーを与えると対応した値が取り出せます。
hash = {"apple" => 200, "orange" => 300, "banana" => 150}
# 値を取得する
p hash["orange"]
# > 300
# 存在しないキーを指定すると nil が返る
p hash["mikan"]
# > nil
値を変更する
ハッシュ["文字列のキー"] = 新しい値
ハッシュ[:シンボルのキー]= 新しい値
[] 角カッコでキーを指定して、値を代入すると値を上書きすることが出来ます。
文字列のキーとシンボルのキーでは書き方が違うので注意しましょう。
hash1 = {"apple" => 200, "orange" => 300, "banana" => 150}
# 値を変更する "banana" の値を 150 -> 180
hash1["banana"] = 180
p hash1
# > {"apple"=>200, "orange"=>300, "banana"=>180}
hash2 = {apple: 200, orange: 300, banana: 150}
# 値を変更する :banana の値を 150 -> 180
hash2[:banana] = 180
p hash2
# > {:apple=>200, :orange=>300, :banana=>180}
要素を追加する
ハッシュ["新しい文字列のキー"] = 値
ハッシュ[:新しいシンボルのキー]= 値
[] 角カッコで新しいキーを指定して、値を代入すると、要素を追加することが出来ます。
hash1 = {"apple" => 200, "orange" => 300, "banana" => 150}
# キー: "cherry"、値: 400 の新しい要素を追加する
hash1["cherry"] = 400
p hash1
# > {"apple"=>200, "orange"=>300, "banana"=>150, "cherry"=>400}
hash2 = {apple: 200, orange: 300, banana: 150}
# キー: :cherry、値: 400 の新しい要素を追加する
hash2[:cherry] = 400
p hash2 # > {:apple=>200, :orange=>300, :banana=>150, :cherry=>400}
# > {:apple=>200, :orange=>300, :banana=>150, :cherry=>400}
要素を削除する
deleteメソッド
ハッシュ.delete("文字列のキー")
ハッシュ.delete(:シンボルのキー)
deleteメソッドで引数にキーを指定すると、対応する要素を削除できます。
hash1 = {"apple" => 200, "orange" => 300, "banana" => 150}
# キーが "orange" の要素を削除する
hash1.delete("orange")
p hash1
# > {"apple"=>200, "banana"=>150}
hash2 = {apple: 200, orange: 300, banana: 150}
# キーが :orange の要素を削除する
hash2.delete(:orange)
p hash2
# > {:apple=>200, :banana=>150}
ハッシュの要素を数える
length、sizeメソッド
ハッシュ.length
ハッシュの要素数を返します。sizeメソッドでも同じ結果が得られます。
hash = {"apple" => 200, "orange" => 300, "banana" => 150}
p hash.length
# > 3
p hash.size
# > 3
countメソッド
ハッシュ.count{ |変数(キー, 値)| 評価式 }
全要素を評価して true になった要素の数を返します。
hash1 = {"apple" => 200, "orange" => 300, "banana" => 150}
# キーに "n" を含む要素の数を数える
p hash1.count{ |key, val| key.include?("n") }
# > 2
# 値が200以上の要素の数を数える
p hash1.count{ |key, val| val >= 200 }
# > 2
hash2 = {apple: 200, orange: 300, banana: 150}
# キーに "n" を含む要素の数を数える
p hash2.count{ |key, val| key.to_s.include?("n") }
# > 2
# 値が200以上の要素の数を数える
p hash2.count{ |key, val| val >= 200 }
# > 2
ハッシュから配列に変換する
keysメソッド
ハッシュ.keys
キーの一覧を配列に変換して返します。
hash1 = {"apple" => 200, "orange" => 300, "banana" => 150}
p hash1.keys
# > ["apple", "orange", "banana"]
hash2 = {apple: 200, orange: 300, banana: 150}
p hash2.keys
# > [:apple, :orange, :banana]
valuesメソッド
ハッシュ.values
値の一覧を配列に変換して返します。
hash1 = {"apple" => 200, "orange" => 300, "banana" => 150}
p hash1.values
# > [200, 300, 150]
hash2 = {apple: 200, orange: 300, banana: 150}
p hash2.values
# > [200, 300, 150]
to_aメソッド
ハッシュ.to_a
キーと値のペアを要素にした配列に変換して返します。
hash1 = {"apple" => 200, "orange" => 300, "banana" => 150}
p hash1.to_a
# > [["apple", 200], ["orange", 300], ["banana", 150]]
hash2 = {apple: 200, orange: 300, banana: 150}
p hash2.to_a
# > [[:apple, 200], [:orange, 300], [:banana, 150]]
ハッシュの要素で繰り返し処理
eachメソッド
ハッシュ.each{ |変数(キー, 値)| 処理 }
キーと配列がペアになった配列で繰り返し処理を行うことが出来ます。
hash1 = {"apple" => 200, "orange" => 300, "banana" => 150}
hash1.each do |key, val|
puts "#{key} は #{val} 円です"
end
# > apple は 200 円です
# > orange は 300 円です
# > banana は 150 円です
hash2 = {apple: 200, orange: 300, banana: 150}
hash2.each do |key, val|
puts "#{key} は #{val} 円です"
end
# > apple は 200 円です
# > orange は 300 円です
# > banana は 150 円です
ハッシュをソートする
sortメソッド
ハッシュ.sort.to_h
sortメソッドを使うと、ハッシュをキーで昇順ソートした配列を返します。
返り値が配列なので、必要なら to_hメソッドを使ってハッシュに戻します。
hash1 = {"apple" => 200, "orange" => 300, "banana" => 150}
# ハッシュのキーでソートする
p hash1.sort
# > [["apple", 200], ["banana", 150], ["orange", 300]]
p hash1.sort.to_h
# > {"apple"=>200, "banana"=>150, "orange"=>300}
hash2 = {:apple: 200, orange: 300, banana: 150}
# ハッシュのキーでソートする
p hash2.sort
# > [[:apple, 200], [:banana, 150], [:orange, 300]]
p hash2.sort.to_h
# > {:apple=>200, :banana=>150, :orange=>300}
sort_byメソッド
ハッシュ.sort_by{ |key, val| val }.to_h
ブロックで評価結果で昇順ソートした配列を返します。
例えば、sort_byメソッドを使って、ハッシュを値でソートすることが出来ます。
返り値が配列なので、必要なら to_hメソッドを使ってハッシュに戻します。
hash1 = {"apple" => 200, "orange" => 300, "banana" => 150}
# ハッシュの値でソートする
p hash1.sort_by{ |key, val| val }
# > [["banana", 150], ["apple", 200], ["orange", 300]]
p hash1.sort_by{ |key, val| val }.to_h
# > {"banana"=>150, "apple"=>200, "orange"=>300}
hash2 = {apple: 200, orange: 300, banana: 150}
# ハッシュの値でソートする
p hash2.sort_by{ |key, val| val }
# > [[:banana, 150], [:apple, 200], [:orange, 300]]
p hash2.sort_by{ |key, val| val }.to_h
# > {:banana=>150, :apple=>200, :orange=>300}
今回のまとめ
- ハッシュは配列に似ているが、位置(インデックス)ではなくキーでデータにアクセスする
- 要素がキーと値のペアになっている配列とハッシュは相互に変換できる
- キーには文字列とシンボルを使うことが出来るが、シンボルを使う方が高速

paiza問題を解くうえでハッシュは必須ではありませんが、覚えておくと便利です。
また、今後アプリを開発する場合、ハッシュの知識も必要になってくると思いますので、今のうちに慣れておくのも良いかもしれませんね