Rails在数组中汇总哈希值

时间:2013-08-22 15:44:33

标签: ruby-on-rails ruby arrays hash

基本上我有一系列像这样的哈希:

[
  { :id => 20, :total => 1, :total2 => 0 },
  { :id => 21, :total => 1, :total2 => 0 },
  { :id => 22, :total => 2, :total2 => 0 },
  { :id => 23, :total => 1, :total2 => 0 },
  { :id => 20, :total => 1, :total2 => 0 },
  { :id => 21, :total => 1, :total2 => 0 },
  { :id => 22, :total => 1, :total2 => 1 },
  { :id => 23, :total => 1, :total2 => 0 }
]

我希望数组将最后两个哈希列相加,保留第一个(:id)作为标识符:

[
  { :id => 20, :total => 2, :total2 => 0 },
  { :id  => 21, :total => 2, :total2 => 0 },
  { :id  => 22, :total => 3, :total2 => 1 }
]

我环顾四周,似乎在这个实例中使用了.inject()方法,但我无法弄清楚语法/如何使用它。

我要找的是将第一列(:id)保留为ID字段;如果有另一个具有此ID的哈希值,就像我上面的例子中那样,两个哈希值应该加在一起。

5 个答案:

答案 0 :(得分:1)

你能试试吗?

array = [{:stemp=>20, :vtotal=>1, :avg=>0}, {:stemp=>21, :vtotal=>1, :avg=>0},{:stemp=>22, :vtotal=>2, :avg=>0}, {:stemp=>23, :vtotal=>1, :avg=>0},  {:stemp=>20, :vtotal=>1, :avg=>0}, {:stemp=>21, :vtotal=>1, :avg=>0}, {:stemp=>22, :vtotal=>1, :avg=>1}, {:stemp=>23, :vtotal=>1, :avg=>0}]

result = array.group_by{|h| h[:stemp]}.map do |stemp, hashes|
  { stemp: stemp, vtotal: hashes.map{|h| h[:vtotal]}.inject(:+), avg: hashes.map{|h| h[:avg]}.inject(:+) }
end

使用ruby 1.9.3将其复制粘贴到IRB控制台中,输出:

[
  {:stemp=>20, :vtotal=>2, :avg=>0},
  {:stemp=>21, :vtotal=>2, :avg=>0},
  {:stemp=>22, :vtotal=>3, :avg=>1},
  {:stemp=>23, :vtotal=>2, :avg=>0}
] 

答案 1 :(得分:0)

我重新格式化了问题和答案中的数据,以便其他人更容易看到正在发生的事情。

data = [
  { :stemp => 20, :vtotal => 1, :avg => 0 },
  { :stemp => 21, :vtotal => 1, :avg => 0 },
  { :stemp => 22, :vtotal => 2, :avg => 0 },
  { :stemp => 23, :vtotal => 1, :avg => 0 },
  { :stemp => 20, :vtotal => 1, :avg => 0 },
  { :stemp => 21, :vtotal => 1, :avg => 0 },
  { :stemp => 22, :vtotal => 1, :avg => 1 },
  { :stemp => 23, :vtotal => 1, :avg => 0 }
]

首先,按stemp

对哈希进行分组
data = data.group_by { |datum| datum[:stemp] }

然后迭代每个stemp及其条目。

data = data.map do |stemp, entries|
  # this pulls out each hash's :vtotal entry and then combines it with the + operator
  vtotal = entries.map { |entry| entry[:vtotal] }.inject(&:+)
  # this does the same as above, but for the avg entry
  avg = entries.map { |entry| entry[:avg] }.inject(&:+)
  { :stemp => stemp, :vtotal => vtotal, :avg => avg }
end

此输出

[
  { :stemp => 20, :vtotal => 2, :avg => 0 },
  { :stemp => 21, :vtotal => 2, :avg => 0 },
  { :stemp => 22, :vtotal => 3, :avg => 1 },
  { :stemp => 23, :vtotal => 2, :avg => 0 }
]

答案 2 :(得分:0)

此解决方案存在可读性,但我想提供它以供参考。

Hash#merge接受在找到碰撞密钥时将执行的块。

arr = [ {:id => 20, :total => 1, :total2 => 0} ... ]

arr.group_by{ |h| h[:id] }.map do |_, hash|
  hash.reduce do |hash_a, hash_b|
    hash_a.merge(hash_b){ |key, old, new| key == :id ? old : old + new }
  end
end

答案 3 :(得分:0)

这个也有效

arr.group_by{|t| t[:stemp]}.map {|key, value| value.inject({}) { |hash, values| values.merge(hash){ |key, v1, v2| key == :stemp ? v1 : v1+v2  }}}

更改为:id

arr.group_by{|t| t[:id]}.map {|key, value| value.inject({}) { |hash, values| values.merge(hash){ |key, v1, v2| key == :id ? v1 : v1+v2  }}}

答案 4 :(得分:0)

[{"4"=>"20.0"}, {"4"=>"20.0"}, {"4"=>"10.0"}, {"4"=>"10.0", "5"=>"10.0"}, {"4"=>"10.0", "5"=>"0.0"}, {"4"=>"10.0"}, {"4"=>"10.0"}, {"4"=>"0.0", "5"=>"10.66"}, {"4"=>"20.0"}, {"4"=>"10.0"}, {"4"=>"10.0"}, {"4"=>"0.0"}].map{|m| m.map{|k,v| v.to_f}.sum()}.sum()