A good way to approach problems like this is to find simpler versions that you can solve, then figure out how to build on that solution to solve the whole thing.
In this case, the simplest subproblem is at the bottom: given a list of two-element lists, produce the corresponding map. In code:
# Given:
simple_attributes = [
["head", "red"],
["head", "green"],
["body", "blue"],
["body", "green"]
]
# Produce:
%{
"head" => ["red", "green"]
"body" => ["blue", "green"]
}
How would you describe this operation in words? I’d say something like “group the list by the first element in each sublist”, and indeed Enum.group_by
is handy:
Enum.group_by(
simple_attributes,
fn [first, _] -> first end
fn [_, second] -> second end
)
This works for simple_attributes
, but it fails for the more complicated list because [_, second]
doesn’t match a three-element row like ["color", "head", "red"]
.
Another way to think of “the second element in a two-element list” is “the TAIL of the list”, and this gets us farther with attributes
:
Enum.group_by(
attributes,
&hd/1,
&tl/1
)
# returns
%{
"color" => [
["head", "red"],
["head", "green"],
["body", "blue"],
["body", "green"]
],
"size" => [
["head", "eye", "green"],
["head", "eye", "black"],
["head", "hair", "red"],
["head", "hair", "green"]
]
}
There’s a promising sign here: the value corresponding to "color"
looks JUST LIKE our original simple_attributes
. Recursion is afoot!
The boundary conditions are tricky to get right (bugs will usually result in either [[too much wrapping]]
or trying to call hd("green")
) but this should get you started.