LoginSignup
2
1

More than 5 years have passed since last update.

Rubyでハッシュの値を元に内訳を再構成する

Posted at

以下のように、日時で入れ子になったハッシュの値にAとBが含まれるとき、Aの日時、Bの日時の一覧が欲しくなったので、ハッシュをこねくり回して目的のデータを作成したい。

input
{ "2016-08-15" => { "18:00" => "A", "19:00" => "B", "20:00" => "A" },
  "2016-08-16" => { "19:00" => "A" } }
output
{ "A" => ["2016-08-15 18:00", "2016-08-15 20:00", "2016-08-16 19:00"],
  "B" => ["2016-08-15 19:00"] }

結論

だいぶ長いが1行で書ける。

    input.map{ |date, daily_hash| daily_hash.inject({}) { |h,(time,v)| (h[v] ||= []) << "#{date} #{time}"; h }  }
      .inject({}){|result, part| result.merge(part){ |_, v, v2| v + v2 } }

…さすがに重たいので順を追って解説する。

解説

外側のmap

細かい処理をhogeに書き換えるとこう。

input.map{ |date, daily_hash| daily_hash.hoge }

まず、inputmapすることで、日付ごとに入れ子になったハッシュが1つづつ取り出されてdatedaily_hashに格納される。

# 1回目: date = "2016-08-15", daily_hash = { "18:00" => "A", "19:00" => "B", "20:00" => "A" }
# 2回目: date = "2016-08-16", daily_hash = { "19:00" => "A" } 

このdaily_hashを1つめのinjectでこねくり回して目的のデータにしていく。

1つめのinject

daily_hash.inject({}) { |h,(time,v)| (h[v] ||= []) << "#{date} #{time}"; h }  }

injectは初期値に対して畳み込みを行っていく。
この場合は空のハッシュが初期値であり、ブロックのhに呼び出されるので、ここに値を追加していきたい。daily_hashの値vをキーとして、日付dateと時刻timeから日時の文字列をつくり、配列h[v]に追加していく。datedaily_hashと同じブロックで取得している。
初期値を定義しないとh[v]nilの状態で<<はできないので、h[v] ||= []で初期化している。
ブロックの中の最後の評価値がinjectで畳み込まれていくので、セミコロンで区切って最後にhを呼んでいる。
8月15日ぶんのデータのみ細かく内訳を追っていこう。

# 初期値:{}
# 1回目: { "A" => ["2015-08-15 18:00"] }
# 2回目: { "A" => ["2015-08-15 18:00"], "B" => ["2015-08-15 19:00"] }
# 3回目: { "A" => ["2015-08-15 18:00", "2015-08-15 20:00"], "B" => ["2015-08-15 19:00"] }

2つめのinject

map〜1つめのinjectfugaに置き換えるとこうなる。

input.fuga.inject({}){|result, part| result.merge(part){ |_, v, v2| v + v2 } }

まず、input.fugaは以下の様な状態になっている。

[{ "A" => ["2015-08-15 18:00", "2015-08-15 20:00"], "B" => ["2015-08-15 19:00"] }, { "A" => ["2015-08-16 19:00"] }]

これをinjectを使ってHash.mergeで畳み込んでいき、ハッシュの配列を1つの配列に統合する。
ここで、mergeにブロックが与えられているのはハッシュの重複対策である。もしこれがなかったとしたら、ハッシュを結合する時に重複したキーAの値が上書きされてしまい、最終データは以下のようになってしまう。

wrong_output
{ "A" => ["2016-08-16 19:00"], "B" => ["2016-08-15 19:00"] }

それでは困るので、キーが重複した場合は競合する値のvv2を足して新しい配列を作る。前の処理で空の配列は初期化されているのでここでは+ができないケースを考える必要はない。

参考資料

2
1
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1