Obviously I don’t care about line count - I like code that is easy to change:
defmodule Demo do
defp initial_total(:response_time, time),
do: if(is_number(time), do: {time, 1}, else: {0, 0})
defp initial_total(_key, value),
do: value || 0
defp initial_aggregate({key, value}, aggregate),
do: Map.put(aggregate, key, initial_total(key, value))
defp merge_total(:response_time, {total_time, count} = total, time),
do: if(is_number(time), do: {total_time + time, count + 1}, else: total)
defp merge_total(_key, total, value),
do: total + (value || 0)
defp merge_aggregate({key, value}, aggregate) do
new_total =
case Map.fetch(aggregate, key) do
{:ok, total} ->
merge_total(key, total, value)
_ ->
initial_total(key, value)
end
Map.put(aggregate, key, new_total)
end
defp item_aggregator({key, data}, stats) do
new_aggregate =
case Map.fetch(stats, key) do
{:ok, aggregate} ->
Enum.reduce(data, aggregate, &merge_aggregate/2)
_ ->
Enum.reduce(data, %{}, &initial_aggregate/2)
end
Map.put(stats, key, new_aggregate)
end
defp finalize_total({:response_time, {total_time, count}}, aggregate) do
if count > 0 do
Map.put(aggregate, :response_time, div(total_time, count))
else
aggregate
end
end
defp finalize_total({key, total}, aggregate) do
Map.put(aggregate, key, total)
end
defp finalize_aggregate({key, aggregate}, stats),
do: Map.put(stats, key, Enum.reduce(aggregate, %{}, &finalize_total/2))
def make_stats(items) do
items
|> List.foldl(%{}, &item_aggregator/2)
|> Enum.reduce(%{}, &finalize_aggregate/2)
end
end
#
# item: {:group_1, data}
# data: %{timeout: 0, failure: 0, hits: 1, response_time: 100}
# value: associated with a "key" inside the "data" Map
# aggregate: %{failure: 0, hits: 2, response_time: {1100,2}, timeout: 0}
# total: value associated with a "key" inside the "aggregate" Map
# finalized_aggregate: %{failure: 0, hits: 2, response_time: 550, timeout: 0}
# stats: %{feed_key => (finalized_)aggregate}
#
feed_items = [
{:group_1, %{timeout: 0, failure: 0, hits: 1, response_time: 100}},
{:group_1, %{timeout: 0, failure: 0, hits: 1, response_time: 1000}},
{:group_2, %{timeout: 0, failure: 0, hits: 1, response_time: 50}},
{:group_2, %{timeout: 0, failure: 0, hits: 1, response_time: 2000}},
{:group_3, %{timeout: 0, failure: 1, hits: 0}},
{:group_3, %{timeout: 1, failure: 1, hits: 0, response_time: nil}}
]
IO.inspect(Demo.make_stats(feed_items))
$ elixir demo.exs
%{
group_1: %{failure: 0, hits: 2, response_time: 550, timeout: 0},
group_2: %{failure: 0, hits: 2, response_time: 1025, timeout: 0},
group_3: %{failure: 2, hits: 0, timeout: 1}
}