Given this data structure:
[
%{ collection: [ %{ description: "Foo", score: 4 }, %{ description: "Bar", score: 5 } ] },
%{ collection: [ %{ description: "Baz", score: 3 }, %{ description: "Foo", score: 4 } ] },
%{ collection: [ %{ description: "Bar", score: 1 }, %{ description: "Baz", score: 1 } ] }
]
I would like to create a set of new maps for each unique description
with a summation of the corresponding scores. Like this:
[
%{ description: "Foo", score: 8 },
%{ description: "Bar", score: 6 },
%{ description: "Baz", score: 4 }
]
One could break this down to the following:
description
I imagine this process will involve a combination of reduce
and map
, but what I'm struggling with is the initial grouping that is necessary to run a summation function. An idiomatic elixir solution would be great. Thanks!
I would use Enum.flat_map/2
and Enum.group_by/2
:
list = [
%{ collection: [ %{ description: "Foo", score: 4 }, %{ description: "Bar", score: 5 } ] },
%{ collection: [ %{ description: "Baz", score: 3 }, %{ description: "Foo", score: 4 } ] },
%{ collection: [ %{ description: "Bar", score: 1 }, %{ description: "Baz", score: 1 } ] }
]
list
|> Enum.flat_map(fn x -> x.collection end)
|> Enum.group_by(fn x -> x.description end)
|> Enum.map(fn {key, value} ->
%{description: key, score: value |> Enum.map(fn x -> x.score end) |> Enum.sum}
end)
|> IO.inspect
Output:
[%{description: "Bar", score: 6}, %{description: "Baz", score: 4},
%{description: "Foo", score: 8}]
Note that since group_by
uses a Map
under the hood, this does not preserve the order of items in the resulting list.
How about this:
defmodule Group do
@groups [
%{ collection: [ %{ description: "Foo", score: 4 }, %{ description: "Bar", score: 5 } ] },
%{ collection: [ %{ description: "Baz", score: 3 }, %{ description: "Foo", score: 4 } ] },
%{ collection: [ %{ description: "Bar", score: 1 }, %{ description: "Baz", score: 1 } ] }
]
def transform(groups \\ @groups) do
groups
|> Enum.reduce([], fn group, list -> list ++ group[:collection] end)
|> Enum.group_by(&(&1.description), &(&1.score))
|> Enum.map(fn {key, v} -> {key, Enum.sum(v)} end)
|> Enum.into(%{})
end
end
iex(24)> Group.transform
%{"Bar" => 6, "Baz" => 4, "Foo" => 8}
Another way:
Enum.reduce(collections, %{}, fn(%{collection: list}, acc) ->
Map.merge(acc, Enum.reduce(list, %{}, fn(map, acc2) -> Map.put(acc2, map[:description], map) end), fn(_k, v1, v2) ->
Map.put(v1, :score, v1[:score] + v2[:score])
end)
end)
|> Map.values()
The last line |> Map.values()
is optional to me. I'd remove this line so that you have a unique key and a map which seems better to me.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.