数组的和和平均数组

时间:2017-06-15 20:04:09

标签: ruby-on-rails ruby

我正在尝试对数组的数组求和并同时获得平均值。原始数据采用begin require 'sqlite3' db = SQLite3::Database.open('test_albums.db') columns = db.execute("pragma table_info(albums)") puts (columns.map { |c| c[1] }).join(' - ') db.execute("select * from albums where ecoute = 2") do |result| puts result.join(' - ') end end 的形式。我必须将我的数据解析为数组数组才能呈现图形。该图不接受散列数组。

我首先使用下面的定义将JSON转换为JSON。

output

上述行动的结果如下所示。

ActiveSupport::JSON.decode(@output.first(10).to_json)

然后我通过转换为数组数组来检索output = [{"name"=>"aaa", "job"=>"a", "pay"=> 2, ... }, {"name"=>"zzz", "job"=>"a", "pay"=> 4, ... }, {"name"=>"xxx", "job"=>"a", "pay"=> 6, ... }, {"name"=>"yyy", "job"=>"a", "pay"=> 8, ... }, {"name"=>"aaa", "job"=>"b", "pay"=> 2, ... }, {"name"=>"zzz", "job"=>"b", "pay"=> 4, ... }, {"name"=>"xxx", "job"=>"b", "pay"=> 6, ... }, {"name"=>"yyy", "job"=>"b", "pay"=> 10, ... }, ] job

pay

以上操作的结果如下。

ActiveSupport::JSON.decode(output.to_json).each { |h| 
  a << [h['job'], h['pay']]
}

下面的代码将以数组数组的形式给出每个元素的总和。

a = [["a", 2], ["a", 4], ["a", 6], ["a", 8],
     ["b", 2], ["b", 4], ["b", 6], ["b", 10]]

结果如下

a.inject({}) { |h,(job, data)| h[job] ||= 0; h[job] += data; h }.to_a

但是,我试图获得数组的平均值。预期产出如下。

[["a", 20], ["b", 22]]

我可以计算数组中有多少元素,并将[["a", 5], ["b", 5.5]] 数组除以sum数组。我想知道是否有更简单,更有效的方法来获得平均值。

5 个答案:

答案 0 :(得分:2)

output = [
  {"name"=>"aaa", "job"=>"a", "pay"=> 2 }, 
  {"name"=>"zzz", "job"=>"a", "pay"=> 4 }, 
  {"name"=>"xxx", "job"=>"a", "pay"=> 6 }, 
  {"name"=>"yyy", "job"=>"a", "pay"=> 8 },
  {"name"=>"aaa", "job"=>"b", "pay"=> 2 }, 
  {"name"=>"zzz", "job"=>"b", "pay"=> 4 }, 
  {"name"=>"xxx", "job"=>"b", "pay"=> 6 }, 
  {"name"=>"yyy", "job"=>"b", "pay"=> 10 }, 
]

output.group_by { |obj| obj['job'] }.map do |key, list|
  [key, list.map { |obj| obj['pay'] }.reduce(:+) / list.size.to_f]
end

group_by方法会将您的列表转换为具有以下结构的哈希:

{"a"=>[{"name"=>"aaa", "job"=>"a", "pay"=>2}, ...], "b"=>[{"name"=>"aaa", "job"=>"b", ...]}

之后,对于每对哈希,我们要计算其'pay'值的平均值,并返回一对[key, mean]。我们使用地图,返回一对:

  1. 他们键入了自己("a""b")。
  2. 价值观的平均值。请注意,值列表具有哈希列表的形式。要检索值,我们需要提取每对的最后一个元素;这是list.map { |obj| obj['pay'] }的用途。最后,通过将所有元素与.reduce(:+)相加并将它们除以列表大小作为浮点数来计算均值。
  3. 不是最有效的解决方案,但它很实用。

    将答案与@ EricDuminil进行比较,这是一个基准,其大小为8.000.000

    def Wikiti(output)
      output.group_by { |obj| obj['job'] }.map do |key, list|
        [key, list.map { |obj| obj['pay'] }.reduce(:+) / list.size.to_f]
      end
    end
    
    def EricDuminil(output)
      count_and_sum = output.each_with_object(Hash.new([0, 0])) do |hash, mem|
        job = hash['job']
        count, sum = mem[job]
        mem[job] = count + 1, sum + hash['pay']
      end
      result = count_and_sum.map do |job, (count, sum)|
        [job, sum / count.to_f]
      end
    end
    
    require 'benchmark'
    
    Benchmark.bm do |x|
      x.report('Wikiti') { Wikiti(output) }
      x.report('EricDuminil') { EricDuminil(output) }
    end
    
    user         system      total        real
    Wikiti       4.100000    0.020000     4.120000 (  4.130373)
    EricDuminil  4.250000    0.000000     4.250000 (  4.272685)
    

答案 1 :(得分:2)

这种方法应该合理有效。它创建一个临时哈希,其作业名称为键,[count, sum]为值:

output = [{ 'name' => 'aaa', 'job' => 'a', 'pay' => 2 },
          { 'name' => 'zzz', 'job' => 'a', 'pay' => 4 },
          { 'name' => 'xxx', 'job' => 'a', 'pay' => 6 },
          { 'name' => 'yyy', 'job' => 'a', 'pay' => 8 },
          { 'name' => 'aaa', 'job' => 'b', 'pay' => 2 },
          { 'name' => 'zzz', 'job' => 'b', 'pay' => 4 },
          { 'name' => 'xxx', 'job' => 'b', 'pay' => 6 },
          { 'name' => 'yyy', 'job' => 'b', 'pay' => 10 }]

count_and_sum = output.each_with_object(Hash.new([0, 0])) do |hash, mem|
  job = hash['job']
  count, sum = mem[job]
  mem[job] = count + 1, sum + hash['pay']
end
#=> {"a"=>[4, 20], "b"=>[4, 22]}

result = count_and_sum.map do |job, (count, sum)|
  [job, sum / count.to_f]
end
#=> [["a", 5.0], ["b", 5.5]]

它需要2次传递,但创建的对象不大。相比之下,在大量哈希上调用group_by效率不高。

答案 2 :(得分:1)

这个怎么样(单程迭代平均计算)

accumulator = Hash.new {|h,k| h[k] = Hash.new(0)}
a.each_with_object(accumulator) do |(k,v),obj|
   obj[k][:count] += 1
   obj[k][:sum] += v
   obj[k][:average] = (obj[k][:sum] / obj[k][:count].to_f)
end
#=> {"a"=>{:count=>4, :sum=>20, :average=>5.0}, 
#     "b"=>{:count=>4, :sum=>22, :average=>5.5}}

显然,平均值只是在每次迭代时重新计算,但由于你同时要求它们,这可能就像你将得到的那样接近。

使用“输出”代替

output.each_with_object(accumulator) do |h,obj|
   key = h['job']
   obj[key][:count] += 1
   obj[key][:sum] += h['pay']
   obj[key][:average] = (obj[key][:sum] / obj[key][:count].to_f)
end

#=> {"a"=>{:count=>4, :sum=>20, :average=>5.0}, 
#     "b"=>{:count=>4, :sum=>22, :average=>5.5}}

答案 3 :(得分:0)

正如Sara Tibbetts评论所暗示的那样,我的第一步就是像这样转换它

new_a = a.reduce({}){ |memo, item| memo[item[0]] ||= []; memo[item[0]] << item[1]; memo}

将其置于此格式

{a: [2, 4, 6, 8], b: [2, 4, 6, 20]}

然后,您可以使用slice过滤所需的键

new_a.slice!(key1, key2, ...)

然后再做一遍以获得最终格式

new_a.reduce([]) do |memo, (k,v)|
  avg = v.inject{ |sum, el| sum + el }.to_f / v.size
  memo << [k,avg]
  memo
end

答案 4 :(得分:0)

我选择使用Enumerable#each_with_object,对象是两个哈希的数组,第一个用于计算总数,第二个用于计算总计数字的数量。每个哈希定义为Hash.new(0),零为默认值。有关更全面的说明,请参阅Hash::new。简而言之,如果定义的哈希h = Hash.new(0)没有密钥k,则h[k]会返回0。 (h未被修改。)h[k] += 1扩展为h[k] = h[k] + 1。如果h没有密钥k,则等式右侧的h[k]会返回0 1

output =
[{"name"=>"aaa", "job"=>"a", "pay"=> 2},
 {"name"=>"zzz", "job"=>"a", "pay"=> 4},
 {"name"=>"xxx", "job"=>"a", "pay"=> 6},
 {"name"=>"yyy", "job"=>"a", "pay"=> 8},
 {"name"=>"aaa", "job"=>"b", "pay"=> 2},
 {"name"=>"zzz", "job"=>"b", "pay"=> 4},
 {"name"=>"xxx", "job"=>"b", "pay"=> 6},
 {"name"=>"yyy", "job"=>"b", "pay"=>10}
]

htot, hnbr = output.each_with_object([Hash.new(0), Hash.new(0)]) do |f,(g,h)|
  s = f["job"]
  g[s] += f["pay"]
  h[s] += 1
end
htot.merge(hnbr) { |k,o,n| o.to_f/n }.to_a
  #=> [["a", 5.0], ["b", 5.5]]

如果删除了末尾的.to_a,则返回散列{"a"=>5.0, "b"=>5.5}。 OP可能会发现它比数组更有用。

我使用了Hash#merge的形式,它使用一个块来确定两个哈希中合并的键的值。

请注意htot={"a"=>20, "b"=>22}hnbr=>{"a"=>4, "b"=>4}

1如果读者想知道为什么h[k]左侧的=也不会返回零,那么这是一种不同的方法:Hash#[]=与{{1} }