Предположим, у меня есть массив вложенных хешей, подобный этому:
array = [
{
"id": 8444,
"version": "2.1.0",
"data": {
"data1": {
"data1-1": {
"a": 132.6,
"b": 128.36,
"c": 153.59,
"d": 136.48
}
},
"data2": {
"data2-1": {
"a": 1283.0,
"b": 1254.0,
"c": 1288.5,
"d": 1329.0
}
}
}
},
{
"id": 8443,
"version": "2.1.0",
"data": {
"data1": {
"data1-1": {
"a": 32.6,
"b": 28.36,
"c": 53.59,
"d": 36.48
}
},
"data2": {
"data2-1": {
"a": 283.0,
"b": 254.0,
"c": 288.5,
"d": 329.0
}
}
}
},
{
"id": 8442,
"version": "2.1.0",
"data": {
"data1": {
"data1-1": {
"a": 32.6,
"b": 28.36,
"c": 53.59,
"d": 36.48
}
},
"data2": {
"data2-1": {
"a": 283.0,
"b": 254.0,
"c": 288.5,
"d": 329.0
}
}
}
}
]
Каждый хэш массива имеет одинаковую структуру карты.
Я хотел бы создать новый хэш с той же структурой хеш-карты, что и data
, и для каждого значения a, b, c, d
иметь среднее значение.
Каков наилучший подход для этого? Потому что я не могу ключ group_by
, так как у меня есть один и тот же ключ в разных подразделах (data1-1
и data2-1
)
Тогда результатом будет:
{
"data1": {
"data1-1": {
"a": 65.9,
"b": 61.7,
"c": 86.9,
"d": 69.8
}
},
"data2": {
"data2-1": {
"a": 616.3,
"b": 587.3,
"c": 621.8,
"d": 662.3
}
}
}
Я пробовал это:
array.reduce({}) do |acc, hash|
hash[:data].each do |k,v|
acc[k] = v
end
end
# => {:data1=>{:"data1-1"=>{:a=>32.6, :b=>28.36, :c=>53.59, :d=>36.48}},
# :data2=>{:"data2-1"=>{:a=>283.0, :b=>254.0, :c=>288.5, :d=>329.0}}}
Давайте сгруппируем хэши, используя .
grouped = array.each_with_object({}) do |h, acc|
h[:data].each do |k, v|
acc[k] ||= []
acc[k] << v
end
end
Результат:
{:data1 => [{:"data1-1" => {:a=>132.6, :b=>128.36, :c=>153.59, :d=>136.48}},
{:"data1-1" => {:a=>32.6, :b=>28.36, :c=>53.59, :d=>36.48}},
{:"data1-1" => {:a=>32.6, :b=>28.36, :c=>53.59, :d=>36.48}}],
:data2 => [{:"data2-1" => {:a=>1283.0, :b=>1254.0, :c=>1288.5, :d=>1329.0}},
{:"data2-1" => {:a=>283.0, :b=>254.0, :c=>288.5, :d=>329.0}},
{:"data2-1" => {:a=>283.0, :b=>254.0, :c=>288.5, :d=>329.0}}]}
Теперь давайте преобразуем эти значения.
grouped = grouped.transform_values do |arr|
k = arr.first.keys.first
arr.collect { |a| {k => a[k]} }.each_with_object({}) do |h, acc|
h.each do |k, v|
acc[k] ||= []
acc[k] << v
end
end
end
# => {:data1=>{:"data1-1"=>[{:a=>132.6, :b=>128.36, :c=>153.59, :d=>136.48},
# {:a=>32.6, :b=>28.36, :c=>53.59, :d=>36.48},
# {:a=>32.6, :b=>28.36, :c=>53.59, :d=>36.48}]},
# :data2=>{:"data2-1"=>[{:a=>1283.0, :b=>1254.0, :c=>1288.5, :d=>1329.0},
# {:a=>283.0, :b=>254.0, :c=>288.5, :d=>329.0},
# {:a=>283.0, :b=>254.0, :c=>288.5, :d=>329.0}]}}
Это гораздо ближе к заявленной вами цели.
Давайте снова преобразуем некоторые значения.
grouped = grouped.transform_values do |v|
k = v.keys.first
values = v.values.first.each_with_object({}) do |h, acc|
h.each do |hk, hv|
acc[hk] ||= []
acc[hk] << hv
end
end
{ k => values }
end
# => {:data1=>{:"data1-1"=>{:a=>[132.6, 32.6, 32.6],
# :b=>[128.36, 28.36, 28.36],
# :c=>[153.59, 53.59, 53.59],
# :d=>[136.48, 36.48, 36.48]}},
# :data2=>{:"data2-1"=>{:a=>[1283.0, 283.0, 283.0],
# :b=>[1254.0, 254.0, 254.0],
# :c=>[1288.5, 288.5, 288.5],
# :d=>[1329.0, 329.0, 329.0]}}}
Даже ближе. Усреднить массив чисел легко. Нам просто нужно трансформироваться values
.
Замена предыдущего бита кода на:
grouped = grouped.transform_values do |v|
k = v.keys.first
values = v.values.first.each_with_object({}) do |h, acc|
h.each do |hk, hv|
acc[hk] ||= []
acc[hk] << hv
end
end
{ k => values.transform_values { |v| v.sum / v.size } }
end
# => {:data1=>{:"data1-1"=>{:a=>65.93333333333334,
# :b=>61.693333333333335,
# :c=>86.92333333333333,
# :d=>69.81333333333333}},
# :data2=>{:"data2-1"=>{:a=>616.3333333333334,
# :b=>587.3333333333334,
# :c=>621.8333333333334,
# :d=>662.3333333333334}}}
Умно и очень хорошо объяснено, большое спасибо, @Chris!
Вы могли бы написать это следующим образом.
fac = 1.fdiv(array.size)
#=> 0.3333333333333333
array.each_with_object({}) do |g,h|
g[:data].each do |k,v|
h[k] ||= {}
v.each do |kk,vv|
h[k][kk] ||= Hash.new(0)
vv.each { |kkk,vvv| h[k][kk][kkk] += fac * vvv }
end
end
end
#=> { :data1=> {
# :"data1-1"=> {
# :a=>65.93333333333334,
# :b=>61.693333333333335,
# :c=>86.92333333333333,
# :d=>69.81333333333332
# }
# },
# :data2=> {
# :"data2-1"=> {
# :a=>616.3333333333334,
# :b=>587.3333333333333,
# :c=>621.8333333333333,
# :d=>662.3333333333333
# }
# }
# }
Обратите внимание, что если arr
представляет собой массив чисел, среднее значение можно вычислить как
(arr.sum).fdiv(arr.size)
или как
fac = 1.fdiv(arr.size)
arr.sum { |m| fac * m }
Я использовал второй метод, чтобы избежать необходимости построения промежуточных массивов.
В качестве альтернативы можно написать следующее.
array.each_with_object(Hash.new { |h,k| h[k] = {} }) do |g,h|
g[:data].each do |k,v|
v.each do |kk,vv|
h[k][kk] ||= Hash.new(0)
vv.each { |kkk,vvv| h[k][kk][kkk] += fac * vvv }
end
end
end
См. Hash::new для объяснения Hash.new(0)
и Hash.new { |h,k| h[k] = {} }
.
Я видел ваш удаленный ответ с попыткой и отредактировал его в вопросе.