We then can do the remainder of the calculation as you did already. But that does require a second pass over the input list. We can do it in a single one:
{sumprod, sum} = Enum.reduce(enum, {0, 0}, fn %{rate: r, amount: a}, {sumprod, sum} -> {(r * a) + sumprod, a + sum} end)
avg = sumprod / sum
There is currently (and probably never will be) no reduce variant that does a finalizing operation when the end of the Enumerable is reached.
Such finalizings have to happen outside of the Enum, and in my opinion somehat destroy pipeability, but to be honest, its darn easy to read and understand.
I do use dedicated helpers only in the pipe, if it were at the end I tend to bind the result of the pipe to a variable and then use the variable in the returning expression. But I think this is a matter of taste.
Especially after piping into anonymous functions or captures is a “bit” ugly
enum
|> Enum.reduce({0,0}, fn %{rate: r, amount: a}, {avg, sum} -> {(r * a + avg * sum) / (a + sum), a + sum} end)
|> elem(0)
(Enum.reduce(enum, fn %{rate: r, amount: a}, %{rate: avg, amount: sum} -> %{ rate: (r * a + avg * sum) / (a + sum), amount: a + sum} end)).rate
The first maintains a tuple that includes the current weighted average after every iteration, so no final calculation is needed.
The second does the same, but using a map. This is a bit more verbose, but cleaner access to the final result. This version does not require an initial accumulator value, as it can just start with the value of the first element.
You should haver mentioned, that your implementations will probably be much slower because of the additional computations made for each element in the list.