Ruby プログラミング

[Ruby]ハッシュ(連想配列)の基本操作

hash

こんにちは!じゃいごテックのあつしです。

今回はハッシュ(連想配列)の基本操作についてご紹介します。

ハッシュ(連想配列)は複数の値をまとめて格納できるオブジェクトで、それぞれの要素はキーのオブジェクトと値がペアになっており、キーを与えることによって要素の値にアクセスすることが出来ます。構造が配列とよく似ていて、共通で使えるメソッドも沢山あります。

ハッシュを生成する

要素を直接入力して生成する

{ キー => 値 }

{} 波カッコ、又は 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問題を解くうえでハッシュは必須ではありませんが、覚えておくと便利です。
また、今後アプリを開発する場合、ハッシュの知識も必要になってくると思いますので、今のうちに慣れておくのも良いかもしれませんね

【PR】Ruby学習でお世話になった本




【PR】アルゴリズム学習でお世話になった本


アルゴリズム関連の技術書は大抵C/C++で書かれていますが、ある程度プログラムが出来れば、処理の流れは理解することが出来ます。






通称: 螺旋本。C++で解説されています。AOJ(Aizu Online Judge)を運営している会津大学の渡辺准教授が書いた本です。データ構造や計算量の内容から丁寧に書いてありますのでアルゴリズム学習の最初の参考書としてオススメです。







通称: 蟻本。C++で解説されています。競技プログラミング中級者の定番書と言われていて、競技プログラミングで利用できる色々なアルゴリズムを学ぶことが出来ます。かなり高度な内容も含まれているので1冊分を完全に理解するのにはかなりの時間がかかりそうですが、手元に置いて何度も読み返しています。







通称: チーター本。C#, C++, JAVAでTopcoderの問題を解説しています。初心者~中級者向けに書かれているので解説が非常に丁寧です。







Python3でアルゴリズムを解説している本です。講義スタイルの本で、図やフローチャートを使ってアルゴリズムとデータ構造についてしっかりと説明されています。代わりにコードは少なめです。Pythonコードが読めなくても十分理解できると思います。


-Ruby, プログラミング
-, ,

© 2024 じゃいごテック