Enum.map - ping list of nested maps

I’ve got a list of maps like:

gin = [
  %{"key0" => %{a: 0, b: 1, c: 2}},
  %{"key1" => %{a: 12, b: 2, c: 1}},
  %{"key2" => %{a: 1, b: 3, c: 12}},
  […]
]

and I need to transform it into:

[
  {"key0", 2},
  {"key1", 1},
  {"key2", 12},
  […]
]

I’ve come up with something like:

Enum.map(gin, &{Enum.at(Map.keys(&1), 0), &1[Enum.at(Map.keys(&1), 0)].c})

which does the job but I have that old gut feeling that it could be done more “proper elixir”-ish. Especially not too proud of those Enum.ats. Any obviously better ways up your experts’ sleeves?

The best solution, IMO: fix whatever’s producing a list of one-element maps when it should be producing a list of tuples.

Absent that, pattern-matching won’t help:

# this does not work, beware
iex(3)> Enum.map(gin, fn %{key => value} -> {key, value.c}; end)

** (CompileError) iex:3: cannot use variable key as map key inside a pattern. Map keys in patterns can only be literals (such as atoms, strings, tuples, and the like) or an existing variable matched with the pin operator (such as ^some_var)
    (stdlib 3.15.2) lists.erl:1267: :lists.foldl/3 
    (elixir 1.13.4) src/elixir_fn.erl:17: anonymous fn/4 in :elixir_fn.expand/4

but the implicit conversion that most Enum functions do will:

iex(3)> Enum.map(gin, fn map -> Enum.at(map, 0); end) 
[
  {"key0", %{a: 0, b: 1, c: 2}},
  {"key1", %{a: 12, b: 2, c: 1}},
  {"key2", %{a: 1, b: 3, c: 12}}
]

then that composes nicely:

iex(4)> gin |> Enum.map(&Enum.at(&1, 0)) |> Enum.map(fn {k, v} -> {k, v.c} end)

[{"key0", 2}, {"key1", 1}, {"key2", 12}]

For comprehensions excel at this sort of thing

iex(1)> gin = [
...(1)>   %{"key0" => %{a: 0, b: 1, c: 2}},
...(1)>   %{"key1" => %{a: 12, b: 2, c: 1}},
...(1)>   %{"key2" => %{a: 1, b: 3, c: 12}}]
iex(2)> for map <- gin, {k, vmap} <- map, do: {k, vmap.c}
[{"key0", 2}, {"key1", 1}, {"key2", 12}]
3 Likes

Could nest reduce too:

Enum.reduce(gin, [], fn map, acc -> 
  Enum.reduce(map, acc, fn {key, value}, acc -> 
    [{key, value.c} | acc] 
  end)   
end) 
|> :lists.reverse()

Something similar was my first approach but I didn’t really like it.

That’s not me :wink: but in fact I can ask if there is any absolutely important reason for putting it out this way. Maybe it can be changed/“fixed”. But the exercise needs to be completed now anyway.

This looks already somewhat nicer to me, although what @gregvaughn suggested:

is probably hard to beat! Or? Now I need to understand how does one come up with that kind of beauty <3