计算Elixir中地图列表中秒数的平均值。

huangapple go评论66阅读模式
英文:

Calculating average of seconds in list of maps Elixir

问题

以下是翻译好的代码部分:

我有这个样本数据

[
%{check_in_time: 60, imaging_time: 180, lab_time: 120, vital_time: 60, pharmacy_time: 100},
%{check_in_time: 20, imaging_time: 380, lab_time: 10, vital_time: 60, pharmacy_time: 200}
]

其中所有这些实体都以秒为单位表示时间。我想要实现的目标是:

计算这些时间的平均值,每个条目表示一个患者的详细信息以及患者在医院中花费的时间,但现在我需要计算这些时间的总平均值,以显示考虑所有患者详细信息的患者的平均等待时间。

我所做的。

items
|> Enum.reduce(
%{
check_in_time: 0,
imaging_time: 0,
lab_time: 0,
pharmacy_time: 0,
vital_time: 0
},
fn %{
   check_in_time: check_in_time,
   imaging_time: imaging_time,
   lab_time: lab_time,
   pharmacy_time: pharmacy_time,
   vital_time: vital_time
 },
 acc ->
%{
  check_in_time: acc.check_in_time + check_in_time,
  imaging_time: acc.imaging_time + imaging_time,
  lab_time: acc.lab_time + lab_time,
  pharmacy_time: acc.pharmacy_time + pharmacy_time,
  vital_time: acc.vital_time + vital_time
}
end
)
|> sum_average()
end

defp sum_average(%{
     check_in_time: check_in_time,
     imaging_time: imaging_time,
     lab_time: lab_time,
     pharmacy_time: pharmacy_time,
     vital_time: vital_time
   }) do
(check_in_time + imaging_time + lab_time + pharmacy_time + vital_time) / 5
end

我希望这能帮助你计算秒钟时间的平均值。如果你有任何其他问题,请随时提出。

英文:

I have this sample data

[
%{  check_in_time: 60,
    imaging_time: 180,
    lab_time: 120,
    vital_time: 60,
    pharmacy_time: 100
},
%{
    check_in_time: 20,
    imaging_time: 380,
    lab_time: 10,
    vital_time: 60,
    pharmacy_time: 200
}
]

where all these entities are presenting time in seconds, What I want to achieve here is:

calculate the average of these times such as each entry presents, a Patients details, and how much time a patient spent in the hospital, but now I need to calculate the grand average of these times, to show the average waiting time for a patient considering all patient's details.

what I did.

    items
    |> Enum.reduce(
      %{
        check_in_time: 0,
        imaging_time: 0,
        lab_time: 0,
        pharmacy_time: 0,
        vital_time: 0
      },
      fn %{
           check_in_time: check_in_time,
           imaging_time: imaging_time,
           lab_time: lab_time,
           pharmacy_time: pharmacy_time,
           vital_time: vital_time
         },
         acc ->
        %{
          check_in_time: acc.check_in_time + check_in_time,
          imaging_time: acc.imaging_time + imaging_time,
          lab_time: acc.lab_time + lab_time,
          pharmacy_time: acc.pharmacy_time + pharmacy_time,
          vital_time: acc.vital_time + vital_time
        }
      end
    )
    |> sum_average()
  end

  defp sum_average(%{
         check_in_time: check_in_time,
         imaging_time: imaging_time,
         lab_time: lab_time,
         pharmacy_time: pharmacy_time,
         vital_time: vital_time
       }) do
    (check_in_time + imaging_time + lab_time + pharmacy_time + vital_time) / 5
  end

I did some R&D and learned this is not the right way to do it. Please guide me on the right way of calculating the average time value in seconds, with the above data. PS: values for those keys can be nil as well.

Any help would be wonderful thank you.

Update: the expected formula and result would be.

    (60 + 20) / 2 (number of values) = 40
    (180 + 380) / 2 = 280
    (120 + 10) / 2 = 65
    (60 + 60) / 2 = 60
    (100 + 200) / 2 = 150

    (40 + 280 + 65 + 60 + 150) / 5 (number of values) = 119

答案1

得分: 1

我认为你可以使用 Map.values/1Enum.sum/1 很简单地完成这个任务,例如:

input = [
  %{  check_in_time: 60, 
    imaging_time: 180,
    lab_time: 120,
    vital_time: 60,
    pharmacy_time: 100
  },
  %{
    check_in_time: 20,
    imaging_time: 380,
    lab_time: 10,
    vital_time: 60,
    pharmacy_time: 200
  }
]

times = input |> Enum.map(fn x -> Map.values(x) end) |> List.flatten() |> Enum.reject(&is_nil/1)
cnt = length(times)
sum = Enum.sum(times)

average = sum / cnt
# 119.0

我认为没有必要维护一个复杂的累加器映射。如果你自己枚举列表一次,性能会稍微好一些,例如:

{sum, cnt} = input |> Enum.reduce({0, 0}, fn x, {sum, cnt} ->
  vals = x |> Map.values() |> Enum.reject(&is_nil/1)
  sum = sum + Enum.sum(vals)
  cnt = cnt + length(vals)
  {sum, cnt}
end)

average = sum / cnt

还有其他一些优化可能,但这两个选项都相当快:运行第一个选项 1000 万次只需 1033 毫秒,而第二个选项只需 773 毫秒。因此,我建议专注于具有最大清晰度和易维护性的解决方案,这意味着尽可能使用内置函数而不是自定义函数。

英文:

I think you can do this fairly simply using Map.values/1 and Enum.sum/1, e.g.

input = [
  %{  check_in_time: 60, 
    imaging_time: 180,
    lab_time: 120,
    vital_time: 60,
    pharmacy_time: 100
  },
  %{
    check_in_time: 20,
    imaging_time: 380,
    lab_time: 10,
    vital_time: 60,
    pharmacy_time: 200
  }
]

times = input |> Enum.map(fn x -> Map.values(x) end) |> List.flatten() |> Enum.reject(&is_nil/1)
cnt = length(times)
sum = Enum.sum(times)

average = sum / cnt
# 119.0

I don't think there's any need to maintain a complicated map of accumulators. Performance is a bit better if you enumerate the list yourself once, e.g.

{sum, cnt} = input |> Enum.reduce({0, 0}, fn x, {sum, cnt} ->
  vals = x |> Map.values() |> Enum.reject(&is_nil/1)
  sum = sum + Enum.sum(vals)
  cnt = cnt + length(vals)
  {sum, cnt}
end)


average = sum / cnt

Other refinements are possible, but both options are reasonably fast: running the first option 10 million (!) times took 1033ms, while the second option took 773m. So I would focus on a solution with the greatest clarity and ease of maintenance, and I think that means using built-in functions over home-rolled ones whenever possible.

答案2

得分: 0

以下是代码部分的翻译:

defmodule My do
  def items do
    [
      %{  check_in_time: 60,
          imaging_time: 180,
          lab_time: 120,
          vital_time: 60,
          pharmacy_time: 100
      },
      %{
          check_in_time: 20,
          imaging_time: 380,
          lab_time: 10,
          vital_time: 60,
          pharmacy_time: 200
      }
    ]
  end
  
  //公共接口:
  def get_average(patients) do
    get_average(patients, [0,0,0,0,0,0])
  end 

  //私有辅助函数:
  defp get_average([], [cit, it, lt, pt, vt, count]) do
    patients_avg = (cit + it + lt + pt + vt)/count
    _category_avg = patients_avg/5
  end
  defp get_average([%{
    check_in_time: check_in_time,
    imaging_time: imaging_time,
    lab_time: lab_time,
    vital_time: vital_time,
    pharmacy_time: pharmacy_time} | patients], 
    [cit, it, lt, vt, pt, count] ) do

      get_average(patients, 
        [
          cit + if is_nil(check_in_time) do 0 else check_in_time end,
          it + if is_nil(imaging_time) do 0 else imaging_time end,
          lt + if is_nil(lab_time) do 0 else lab_time end,
          vt + if is_nil(vital_time) do 0 else vital_time end,
          pt + if is_nil(pharmacy_time) do 0 else pharmacy_time end,
          count + 1
        ]
      ) 
  end
end

在iex中的输出:

iex(1)> items = My.items()
[
  %{
    check_in_time: 60,
    imaging_time: 180,
    lab_time: 120,
    pharmacy_time: 100,
    vital_time: 60
  },
  %{
    check_in_time: 20,
    imaging_time: 380,
    lab_time: 10,
    pharmacy_time: 200,
    vital_time: 60
  }
]
iex(2)> My.get_average(items)
119.0

if is_nil() 语句可以改为更短但更难理解的方式:

cit + (check_in_time || 0),
it + (imaging_time || 0),
lt + (lab_time || 0),
vt + (vital_time || 0),
pt + (pharmacy_time || 0),

但你可能需要考虑使用 is_number(),因为如果 check_in_time"a",那么 || 将返回 "a" 而不是 0 - is_nil() 也会如此。

英文:

The following code just does reduce() by hand so that on the final iteration of the list, i.e. when the list is empty, the code can calculate the average:

defmodule My do
  def items do
    [
      %{  check_in_time: 60,
          imaging_time: 180,
          lab_time: 120,
          vital_time: 60,
          pharmacy_time: 100
      },
      %{
          check_in_time: 20,
          imaging_time: 380,
          lab_time: 10,
          vital_time: 60,
          pharmacy_time: 200
      }
    ]
  end
  
  //Public interface:
  def get_average(patients) do
    get_average(patients, [0,0,0,0,0,0])
  end 

  //Private helper function:
  defp get_average([], [cit, it, lt, pt, vt, count]) do
    patients_avg = (cit + it + lt + pt + vt)/count
    _category_avg = patients_avg/5
  end
  defp get_average([%{
    check_in_time: check_in_time,
    imaging_time: imaging_time,
    lab_time: lab_time,
    vital_time: vital_time,
    pharmacy_time: pharmacy_time} | patients], 
    [cit, it, lt, vt, pt, count] ) do

      get_average(patients, 
        [
          cit + if is_nil(check_in_time) do 0 else check_in_time end,
          it + if is_nil(imaging_time) do 0 else imaging_time end,
          lt + if is_nil(lab_time) do 0 else lab_time end,
          vt + if is_nil(vital_time) do 0 else vital_time end,
          pt + if is_nil(pharmacy_time) do 0 else pharmacy_time end,
          count + 1
        ]
      ) 
  end
end

In iex:

/elixir_programs% iex a.ex
Erlang/OTP 24 [erts-12.3.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1]

Interactive Elixir (1.14.4) - press Ctrl+C to exit (type h() ENTER for help)


iex(1)> items = My.items()
[
  %{
    check_in_time: 60,
    imaging_time: 180,
    lab_time: 120,
    pharmacy_time: 100,
    vital_time: 60
  },
  %{
    check_in_time: 20,
    imaging_time: 380,
    lab_time: 10,
    pharmacy_time: 200,
    vital_time: 60
  }
]
iex(2)> My.get_average(items)
119.0

The if is_nil() statements can be changed to the shorter but more cryptic:

      cit + (check_in_time || 0),
      it + (imaging_time || 0),
      lt + (lab_time || 0),
      vt + (vital_time || 0),
      pt + (pharmacy_time || 0),

But, you might want to consider using is_number() because if check_in_time is "a", then || will return "a" not 0--as will is_nil().

huangapple
  • 本文由 发表于 2023年8月4日 02:31:31
  • 转载请务必保留本文链接:https://go.coder-hub.com/76830773.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定