将哈希数组操作为带数组的分组哈希

时间:2017-05-28 22:58:07

标签: arrays ruby hash

我有一系列哈希:

[
    {
        "June" => { "A" => { 3 => 48.4 } }
    },
    {
        "January" => { "C" => { 2 => 88.0} }
    },
    {
        "January"=> { "B" => { 2 => 44.0} }
    },
    {
        "January"=> { "C" => { 4 => 48.8} }
    }
]

我需要将每个类似的哈希键分组为后续值的数组,如下所示:

{
    "June" => [{ "A" => [{ 3 => 48.4 }]] },
    "January" => [
        { "B" => [{ 2 => 44.0}],
        { "C" => [{ 2 => 88.0}, { 4 => 48.8}],
        ] }
}

我正在寻找一种有效的方法来分组这些元素。任何人都可以帮助我掌握哈希的散列吗?

我试图避免循环通过基本数组并手动分组。我希望map(或其他一些可枚举的方法)可以提供我想要的东西。当我使用reduce(Hash.new, :merge)时,它接近但它使用了每月键的最后一个哈希,而不是将它添加到数组中。

1 个答案:

答案 0 :(得分:2)

注意:在更清楚地了解问题之后,我添加了以下内容。我原来的答案如下。

这是OP的哈希数组,略有修改。

arr = [{ "June"   =>{ "A"=>{ 3=>48.4 } } },
       { "January"=>{ "C"=>{ 2=>88.0 } } },
       { "January"=>{ "B"=>{ "D"=>{ 2=>44.0 } } } },
       { "January"=>{ "C"=>{ 2=>10.0 } } },
       { "January"=>{ "C"=>{ 4=>48.8 } } }]

要构造的哈希似乎如下。

{ "June"   =>[{ "A"=>[{ 3=>48.4 }] }],
  "January"=>[{ "B"=>[{ "D"=>[{ 2=>44.0 }] }] }],
                "C"=>[{ 2=>98.0, 4=>48.8 }] }] }

请注意88.0 + 10.0 #=> 98.0中的2=>98.0

观察arr中的所有数组都包含一个元素,一个哈希。既然如此,那些阵列没有任何用处。因此,我建议改为构造以下哈希:

{ "June"   =>{ "A"=>{ 3=>48.4 } },
  "January"=>{ "B"=>{ "D"=>{ 2=>44.0 } } },
               "C"=>{ 2=>98.0, 4=>48.8 } } }

这可以使用以下递归方法生成。

def recurse(arr)
  arr.map(&:flatten).
      group_by(&:first).
      each_with_object({}) do |(k,v),h|
        o = v.map(&:last)
        h.update(k=>o.first.is_a?(Hash) ? recurse(o) : o.sum )
      end
 end

 recurse(arr)
   #=> {"June"=>{"A"=>{3=>48.4}},
   #    "January"=>{"C"=>{2=>98.0, 4=>48.8}, "B"=>{"D"=>{2=>44.0}}}}

(原始答案如下)

以下是获取所需哈希的两种方法。我假设arr是你的哈希数组。

#1使用Hash::new的格式

arr.each_with_object(Hash.new { |h,k| h[k] = [] }) do |g,h|
  k, v = g.to_a.first
  h[k] << v
end
  # => {"June"=>[{"A"=>{3=>48.4}}],
  #     "January"=>[{"C"=>{2=>88.0}}, {"B"=>{2=>44.0}}, {"C"=>{4=>48.8}}]}

#2使用Enumerable#group_by

arr.map(&:first).
    group_by(&:first).
    tap { |h| h.keys.each { |k| h[k] = h[k].map(&:last) } }
  # => {"June"=>[{"A"=>{3=>48.4}}],
  #     "January"=>[{"C"=>{2=>88.0}}, {"B"=>{2=>44.0}}, {"C"=>{4=>48.8}}]}

步骤如下。

a = arr.map(&:first)
  #=> [["June", {"A"=>{3=>48.4}}], ["January", {"C"=>{2=>88.0}}],
  #    ["January", {"B"=>{2=>44.0}}], ["January", {"C"=>{4=>48.8}}]]
b = a.group_by(&:first)
  #=> {"June"=>[["June", {"A"=>{3=>48.4}}]],
  #    "January"=>[["January", {"C"=>{2=>88.0}}], ["January", {"B"=>{2=>44.0}}],
  #                ["January", {"C"=>{4=>48.8}}]]}
c = b.tap { |h| h.keys.each { |k| h[k] = h[k].map(&:last) } }
  #=> {"June"=>[{"A"=>{3=>48.4}}],
  #    "January"=>[{"C"=>{2=>88.0}}, {"B"=>{2=>44.0}}, {"C"=>{=>48.8}}]}

让我详细说明最后一步。在tap块内,我们计算以下内容。

h = b
d = h.keys
  #=> ["June", "January"]

d的第一个元素被传递给each的块,块变量被赋给该元素。

k = d.first
  #=> "June"

块计算如下。

e = h[k]
  #=> [["June", {"A"=>{3=>48.4}}]]
f = e.map(&:last)
  #=> [{"A"=>{3=>48.4}}]
h[k] = f
  #=> [{"A"=>{3=>48.4}}]
b #=> {"June"=>[{"A"=>{3=>48.4}}],
  #    "January"=>[["January", {"C"=>{2=>88.0}}],
  #                ["January", {"B"=>{2=>44.0}}],
  #                ["January", {"C"=>{4=>48.8}}]]}

接下来,将d[1](“1月”)传递给each块,并执行类似的计算。

而不是使用Object#tap我可以写

h = arr.map(&:first).
    group_by(&:first)
h.keys.each { |k| h[k] = h[k].map(&:last) }
h

tap仅避免创建局部变量h,并且需要使最终行等于h