Rubyを書いていると、「Array(配列)をHash(ハッシュ)に変換したい」という場面は意外と多く出てきます。
例えば、APIレスポンスを加工してキーと値を対応付けたいとき、集計結果を整理したいとき、Railsでフォーム入力を扱うときなど。
しかし、一口に「配列からハッシュを作る」と言ってもやり方はいくつもあり、どれを選ぶかでコードの読みやすさや意図の伝わり方が大きく変わります。
この記事では、Ruby標準の機能からRails拡張メソッドまで、ArrayからHashを作る代表的な方法を体系的に紹介します。
「どの方法を使えばよいのか?」が自然に判断できるようになることを目指します。
[key, value] ペアを要素とするArrayをHashに変換する
もっとも基本的なパターンです。
Arrayの各要素が [key, value] のペアで構成されている場合、Ruby標準の Array#to_h を使えば一発です。
[
[:a, "A"], [:b, "B"], [:c, "C"], [:d, "D"], [:e, "E"]
].to_h
#=> {:a=>"A", :b=>"B", :c=>"C", :d=>"D", :e=>"E"}
シンプルで直感的。Ruby 2.1以降で利用できます。
「すでにペア構造を持っている配列をハッシュ化する」ケースでは、まずこれを使いましょう。
2つのArrayを結合してHashにする
キーと値が別々の配列として存在するケースも多いですよね。
その場合に使えるのが Array#zip や Array#transpose です。
Array#zip + to_h
zip は2つの配列を「横に」結合し、それを to_h で変換します。
[:a, :b, :c, :d, :e].zip(["A", "B", "C", "D", "E"]).to_h
#=> {:a=>"A", :b=>"B", :c=>"C", :d=>"D", :e=>"E"}
Array#transpose + to_h
transpose は「縦横を入れ替える」メソッドです。
2つの配列をひとつの配列にまとめてから、行と列を入れ替えます。
[
[:a, :b, :c, :d, :e],
["A", "B", "C", "D", "E"]
].transpose.to_h
#=> {:a=>"A", :b=>"B", :c=>"C", :d=>"D", :e=>"E"}
見た目はやや異なりますが、結果は同じです。
zip のほうがコードの意図が分かりやすく、実務でもよく使われます。
Arrayを逐次評価してHashを作る
単に配列の要素を変換するだけでなく、処理を挟みながらHashを構築したい場合に便利な方法を紹介します。
Array#to_h(ブロック付き)
ブロックを渡すと、要素ごとに [key, value] のペアを返す形で処理できます。
%w[a b c d e].to_h do |alphabet|
[alphabet.to_sym, alphabet.upcase]
end
#=> {:a=>"A", :b=>"B", :c=>"C", :d=>"D", :e=>"E"}
1行で「配列の要素を加工しつつハッシュに変換」ができる、Rubyらしい書き方です。
Enumerable#inject
Enumerable#inject は畳み込み操作。
初期値に空ハッシュ {} を与え、ブロック内で都度値を追加していきます。
%w[a b c d e].inject({}) do |acc, alphabet|
acc[alphabet.to_sym] = alphabet.upcase
acc
end
#=> {:a=>"A", :b=>"B", :c=>"C", :d=>"D", :e=>"E"}
acc に都度値を追加して返す構造が少し冗長ですが、柔軟性が高く、条件付き追加など複雑な処理に向いています。
Enumerable#each_with_object
Enumerable#each_with_object は、inject の「より読みやすい」版といえます。
%w[a b c d e].each_with_object({}) do |alphabet, acc|
acc[alphabet.to_sym] = alphabet.upcase
end
#=> {:a=>"A", :b=>"B", :c=>"C", :d=>"D", :e=>"E"}
こちらはブロックの戻り値を意識しなくてよく、可読性が高いです。
ハッシュ構築で迷ったらまず each_with_object を選ぶとよいでしょう。
ArrayをHashに集約する(集計・分類)
配列を集約・グルーピングしてハッシュにしたい場合は、Enumerable#tally と Enumerable#group_by が強力です。
Enumerable#tally
各要素の出現回数を自動で数えてくれます(Ruby 2.7以降)。
%w[a b b B c c c C C d D].tally
#=> {"a"=>1, "b"=>2, "B"=>1, "c"=>3, "C"=>2, "d"=>1, "D"=>1}
特にログ解析や統計処理で重宝します。
大文字・小文字を区別したくない場合は、map(&:downcase).tally のように前処理を加えるのが定番です。
Enumerable#group_by
条件ごとに要素を分類してHashにする場合はこちら。
%w[a b b B c c c C C d D]
.group_by { |alphabet| /^[a-z]+$/.match?(alphabet) ? 'lower' : 'upper' }
#=> {"lower"=>["a", "b", "b", "c", "c", "c", "d"], "upper"=>["B", "C", "C", "D"]}
グルーピング後に .transform_values(&:count) のように加工すれば、
「分類別の件数」を1行で求めることもできます。
Railsで使える便利メソッド
Rails(ActiveSupport)環境では、さらに便利なメソッドが追加されています。
Ruby標準よりも直感的に使えるため、実務での登場頻度も高いです。
Enumerable#index_with
キーを配列の要素、値をブロックの戻り値にしてHashを作ります。
%w[a b c d e].index_with(&:upcase)
#=> {"a"=>"A", "b"=>"B", "c"=>"C", "d"=>"D", "e"=>"E"}
Ruby 3.1 でもほぼ同名のメソッド(with_index)が追加されましたが、
Railsでは古くからサポートされており、ほぼ同じ挙動です。
Enumerable#index_by
逆に、ブロックの結果をキー、元の要素を値とするのが index_by です。
%w[a b c d e].index_by(&:upcase)
#=> {"A"=>"a", "B"=>"b", "C"=>"c", "D"=>"d", "E"=>"e"}
APIレスポンスやデータベース結果を「特定の属性でキー化したい」ケースに非常に便利です。
まとめ
Rubyの世界では、ArrayからHashを作る手段が非常に豊富です。
どの方法を選ぶかは「目的」と「データ構造」によって変わります。
| 目的 | 推奨メソッド |
|---|---|
[key, value] ペアをHash化 |
to_h |
| 2つの配列からHash | zip または transpose |
| 処理しながら構築 | to_h {}, each_with_object |
| 集約・グループ化 | tally, group_by |
| Rails環境で簡潔に | index_with, index_by |
コードを短く書くことが目的ではなく、「意図が伝わる書き方をする」ことが重要です。
Rubyにはそのための表現力が備わっています。
ぜひ状況に合わせて、最適な変換メソッドを選んでみてください。