こんにちは!じゃいごテックのあつしです。
今回はハッシュ(連想配列)の基本操作についてご紹介します。
ハッシュ(連想配列)は複数の値をまとめて格納できるオブジェクトで、それぞれの要素はキーのオブジェクトと値がペアになっており、キーを与えることによって要素の値にアクセスすることが出来ます。構造が配列とよく似ていて、共通で使えるメソッドも沢山あります。
ハッシュを生成する
要素を直接入力して生成する
{ キー => 値 }
{}
波カッコ、又は 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問題を解くうえでハッシュは必須ではありませんが、覚えておくと便利です。
また、今後アプリを開発する場合、ハッシュの知識も必要になってくると思いますので、今のうちに慣れておくのも良いかもしれませんね