Среднее значение массива вложенных хэшей с одинаковой структурой

Предположим, у меня есть массив вложенных хешей, подобный этому:

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}}}

Я видел ваш удаленный ответ с попыткой и отредактировал его в вопросе.

Chris 03.11.2022 19:13
Пошаговое руководство по созданию собственного Slackbot: От установки до развертывания
Пошаговое руководство по созданию собственного Slackbot: От установки до развертывания
Шаг 1: Создание приложения Slack Чтобы создать Slackbot, вам необходимо создать приложение Slack. Войдите в свою учетную запись Slack и перейдите на...
0
1
78
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Давайте сгруппируем хэши, используя .

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!

ZazOufUmI 03.11.2022 21:48

Вы могли бы написать это следующим образом.

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] = {} }.

Другие вопросы по теме