一から勉強させてください

最下級エンジニアが日々の学びをアウトプットしていくだけのブログです

RubyでネストしたHashをflatな1次元のHashに変換する

{
  :foo => "bar",
  :hello => {
    :world => "Hello World",
    :bro => "What's up dude?",
  },
  :a => {
    :b => {
      :c => "d"
    }
  }
}

みたいなネストした Hash があったとして、これを

{
  :foo => "bar",
  :"hello.world" => "Hello World",
  :"hello.bro" => "What's up dude?",
  :"a.b.c" => "d"
}

にしたい時がきっとあると思います。

今回それを実現するためのメソッドをStack Overflow から丸パクリしたので書いたので、メモ。

こんな感じ。引数にネストした Hash を渡すと目的の Hash が返ってくるはず。

def flatten_hash_from hash
  hash.each_with_object({}) do |(key, value), memo|
    next flatten_hash_from(value).each do |k, v|
      memo["#{key}.#{k}".intern] = v
    end if value.is_a? Hash
    memo[key] = value
  end
end

each_with_objectを使う。初期値{}をセットして、これに欲しい情報をどんどん突っ込んでいって目的の Hash を完成させる。injectがブロックの最後に評価した値がmemoにセットされていくのに対して、each_with_objectは常に最初に渡したこのオブジェクトだけを見ていてくれるのでわかりやすい。そして flat になるまでひたすら再帰処理をする。

実際に上述のネストした Hash に対して適用した場合を考えて、処理の流れを追ってみる。

  1. まずkey = :foo, value = "bar"。これはvalueが Hash ではないのでmemo{ :foo => "bar" }となって次へ。

  2. 次にkey = :hello, value = { :world => "Hello World", :bro => "What's up dude?" }。これはvalueが Hash なので、このvalueに対してさらにflatten_hash_fromメソッドが呼ばれる。

  3. 再帰処理の中のmemoは新たな{}であり、1 のmemoとは異なるので注意。まずkey = :world, value = "Hello World"。これはvalueが Hash ではないので、memo{ :world => "Hello World" }となる。key = :broについても同様なので、最終的にmemo{ :world => "Hello World", :bro => "What's up dude?" }となり、これが 2 のflatten_hash_from(value)に返る。

  4. 3 からさらにeach処理へ。ここでkey = :helloになっているので、memo["#{key}.#{k}".intern] = vとすることで、memokeyhello.world, hello.broのようになる。最終的にこの処理の後のmemo{ :foo => "bar", :"hello.world" => "Hello World", :"hello.bro" => "What's up dude?" }になる。

  5. 次にkey = :a, value = { :b => { :c => "d" } }。これもvalueが Hash なので再帰処理へ。

  6. key = :b, value = { :c => "d" }なのでさらに再帰処理へ。

  7. key = :c, value = "d"なので { :c => "d" }が 6 のflatten_hash_fromに返る。

  8. { :"b.c" => "d" }となり、5 のflatten_hash_fromに返る。

  9. key = :aの階層に戻ってきてeachするので{:"a.b.c" => "d" }が新たにmemoに追加される。その結果、memo{ :foo => "bar", :"hello.world" => "Hello World", :"hello.bro" => "What's up dude?", :"a.b.c" => "d" }となるので終了。

まとめ

再帰処理はちゃんと処理の流れを理解して使っていきたい。

参考