Strategies for transforming collections of maps based nested collections

I wanted to get peoples opinions on strategies for transforming, say, a list of maps based on some nested collection.

Take this data for example:

groups = [
  %{group: 1, members: [%{name: "John", active: true}, %{name: "Jill", active: false}]},
  %{group: 2, members: [%{name: "Rob", active: false}, %{name: "Eli", active: false}]},
  %{group: 3, members: [%{name: "Janice", active: true}, %{name: "Sue", active: true}]}
]

A list of maps each with a nested list of maps (members). Say I want a list of groups but without members that are active: false AND filter out the groups that end up having no active members.

After taking a stab at trying to |> functions I settled on a good ol’ list comprehension.

groups = (for group <- groups do
  active_members = Enum.reject(group.members, fn(m) -> m.active == false end)
  put_in(group, [:members], active_members)
end)
|> Enum.filter(fn(group) -> length(group.members) > 0 end)

IO.inspect(groups)

Which gives back what you would expect:

[%{group: 1, members: [%{active: true, name: "John"}]},
 %{group: 3,
   members: [%{active: true, name: "Janice"}, %{active: true, name: "Sue"}]}]

I tried for a while to make some kind of |> transformation and couldn’t figure it out. Perhaps I was trying to hard to make something fit into the |> style. Feel free to share your suggestions.

This got me thinking is there any good Cookbook style resources that can point you in the right direction for tackling certain problems in idiomatic Elixir? Thoughts?

1 Like

Eh, I’d use a for comprehension for sure, but your for comprehension has no comprehension at all, yours is just a loop/map, here is a more traditional for comprehension:

iex(1)> groups = [
...(1)>   %{group: 1, members: [%{name: "John", active: true}, %{name: "Jill", active: false}]},
...(1)>   %{group: 2, members: [%{name: "Rob", active: false}, %{name: "Eli", active: false}]},
...(1)>   %{group: 3, members: [%{name: "Janice", active: true}, %{name: "Sue", active: true}]}
...(1)> ]
[%{group: 1,
   members: [%{active: true, name: "John"}, %{active: false, name: "Jill"}]},
 %{group: 2,
   members: [%{active: false, name: "Rob"}, %{active: false, name: "Eli"}]},
 %{group: 3,
   members: [%{active: true, name: "Janice"}, %{active: true, name: "Sue"}]}]
iex(2)> for group <- groups,
...(2)>     members = Enum.filter(group.members, &(&1.active)),
...(2)>     members != [],
...(2)>     do: %{group | members: members}
[%{group: 1, members: [%{active: true, name: "John"}]},
 %{group: 3,
   members: [%{active: true, name: "Janice"}, %{active: true, name: "Sue"}]}]

For a pipeline, which I would not recommend here because it is editing multiple layers of nesting:

iex(1)> groups = [
...(1)>   %{group: 1, members: [%{name: "John", active: true}, %{name: "Jill", active: false}]},
...(1)>   %{group: 2, members: [%{name: "Rob", active: false}, %{name: "Eli", active: false}]},
...(1)>   %{group: 3, members: [%{name: "Janice", active: true}, %{name: "Sue", active: true}]}
...(1)> ]
[%{group: 1,
   members: [%{active: true, name: "John"}, %{active: false, name: "Jill"}]},
 %{group: 2,
   members: [%{active: false, name: "Rob"}, %{active: false, name: "Eli"}]},
 %{group: 3,
   members: [%{active: true, name: "Janice"}, %{active: true, name: "Sue"}]}]
iex(2)> groups |> Enum.map(fn g -> %{g | members: Enum.filter(g.members, &(&1.active))} end) |> Enum.filter(&(&1.members != []))
[%{group: 1, members: [%{active: true, name: "John"}]},
 %{group: 3,
   members: [%{active: true, name: "Janice"}, %{active: true, name: "Sue"}]}]
2 Likes

Thank you @OvermindDL1, clearly I need to go back to functional programming school for a bit longer.

1 Like

Just practice, keep doing more! :slight_smile:

1 Like