我正在尝试对数组的数组求和并同时获得平均值。原始数据采用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
数组。我想知道是否有更简单,更有效的方法来获得平均值。
答案 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]
。我们使用地图,返回一对:
"a"
或"b"
)。list.map { |obj| obj['pay'] }
的用途。最后,通过将所有元素与.reduce(:+)
相加并将它们除以列表大小作为浮点数来计算均值。不是最有效的解决方案,但它很实用。
将答案与@ 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)
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} }