一から勉強させてください( ̄ω ̄;)

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

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" }となるので終了。

まとめ

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

参考