[Ruby]どうしても配列の中身のハッシュを平坦化させたい


はじめに

配列とハッシュが混合したものを扱う際に、少々手こずったのでメモ程度に書き起こしておく。
結果的に少々泥臭さのあるコードとなってしまった気がするので、より自然で簡潔なものがあれば、ご指摘していただけると助かります。

配列の中のハッシュを平坦化

今回躓いていたのは、配列の中のハッシュの平坦化
どうにかして、①の配列を②の配列にしたい。

# ①の配列
[
  [{"id"=>1}, {"name"=>"Bob", "gender"=>"m", "birthday"=>"2010/01/01"}],
  [{"id"=>5}, {"name"=>"Tom", "gender"=>"m", "birthday"=>"2011/11/01"}],
  [{"id"=>8}, {"name"=>"Alice", "gender"=>"w", "birthday"=>"2012/02/01"}]
]
# ②の配列
[
  [{"id"=>1, "name"=>"Bob", "gender"=>"m", "birthday"=>"2010/01/01"}],
  [{"id"=>5, "name"=>"Tom", "gender"=>"m", "birthday"=>"2011/11/01"}],
  [{"id"=>8, "name"=>"Alice", "gender"=>"w", "birthday"=>"2012/02/01"}]
]

要するに配列の中身のハッシュを平坦化?というか一つのハッシュにするという作業。

そもそもの目的

そもそもなぜそんなことをする必要が出てくるのかという話から。
idsdetailsという2つの配列がある。

ids = [
  { 'id' => 1 },
  { 'id' => 5 },
  { 'id' => 8 }
]

details = [
  {
    'name' => 'Bob',
    'gender' => 'm',
    'birthday' => '2010/01/01'
  },
  {
    'name' => 'Tom',
    'gender' => 'm',
    'birthday' => '2011/11/01'
  },
  {
    'name' => 'Alice',
    'gender' => 'w',
    'birthday' => '2012/02/01'
  }
]

この2つの配列は1:1で対応していて、レスポンスで返す際には、これらをくっつけた状態で返したい。
※ id=1Bobの情報。id=5Tomの情報...

そして、最終的に得たい形は次のハッシュと配列の組み合わせであった。
※ countidsの数

{
  "count"=>3,
  "details"=> [
    {"id"=>1, "name"=>"Bob", "gender"=>"m", "birthday"=>"2010/01/01"},
    {"id"=>5, "name"=>"Tom", "gender"=>"m", "birthday"=>"2011/11/01"},
    {"id"=>8, "name"=>"Alice", "gender"=>"w", "birthday"=>"2012/02/01"}
  ]
}

そのため、配列の中身のハッシュを平坦化させるという作業が必要になる。

行ったこと

zipメソッド

zipメソッドは自身の要素と、引数で与えた配列の要素を同じインデックス同士で組み合わせた配列を返す。
今回のように同じインデックス同士で1対1対応しているものにもってこいのメソッドである。

details_after_zip = ids.zip(details)
#=> [[{"id"=>1}, {"name"=>"Bob", "gender"=>"m", "birthday"=>"2010/01/01"}], [{"id"=>5}, {"name"=>"Tom", "gender"=>"m", "birthday"=>"2011/11/01"}], [{"id"=>8}, {"name"=>"Alice", "gender"=>"w", "birthday"=>"2012/02/01"}]]

ここで最初の方で述べた、「配列の中身のハッシュを平坦化したい」という話に繋がる。
次の方法で一応解決はした。

mergeメソッド

mergeメソッドは、2つのハッシュを統合するというもの。

bob_info = details_after_zip[0]
bob_info[0].merge(bob_info[1])
#=> {"id"=>1, "name"=>"Bob", "gender"=>"m", "birthday"=>"2010/01/01"}

このメソッドをループさせて利用する。

details_hash_flatten = details_after_zip.map do |d|
  d[0].merge(d[1])
end

#=>[{"id"=>1, "name"=>"Bob", "gender"=>"m", "birthday"=>"2010/01/01"}, {"id"=>5, "name"=>"Tom", "gender"=>"m", "birthday"=>"2011/11/01"}, {"id"=>8, "name"=>"Alice", "gender"=>"w", "birthday"=>"2012/02/01"}]

これで準備は整ったので、当初の目的の形にする。

{
  'count' => ids.size,
  'detail' => details_hash_flatten
}

#=>{ "count"=>3, "detail"=>[ {"id"=>1, "name"=>"Bob", "gender"=>"m", "birthday"=>"2010/01/01"}, {"id"=>5, "name"=>"Tom", "gender"=>"m", "birthday"=>"2011/11/01"}, {"id"=>8, "name"=>"Alice", "gender"=>"w", "birthday"=>"2012/02/01"}] }

おわりに

mergeメソッドをループさせるという解決方法をとった。
しかし、もっと良い方法があったのかもしれない。
それこそ「配列の中身のハッシュを平坦化させる」といったものはあるのだろうか。

追記

自分で書いていて感じたことではあったのですが、添字の[0]とかはダサいし、あんまり良くはないので避けたいとのご指摘。

reduceメソッド(またはinject)を使用したら、よりクリーンな形に。

ids.zip(details).map { |a| a.reduce(&:merge) }
#=> [{"id"=>1, "name"=>"Bob", "gender"=>"m", "birthday"=>"2010/01/01"}, {"id"=>5, "name"=>"Tom", "gender"=>"m", "birthday"=>"2011/11/01"}, {"id"=>8, "name"=>"Alice", "gender"=>"w", "birthday"=>"2012/02/01"}]

こっちのが良いですね。