Map over a map and create a new map?

Hello! I’m trying to get more comfy with idiomatic elixir, and it seems like one line anonymous fn {} -> ... end calls might not be ideal. Maybe they are, but I’m curious if there’s a cleaner way to do a map over a map and get another map.

Here’s what I have:

Enum.map(m, fn {k,v}-> {k, length(v)} end) |> Map.new

Is there a way to write this more concisely? At first I thought capture syntax might be the way, but I can’t find a function to capture that creates a tuple.


And for bonus points, can you destructure capture arguments? Because I also have one of these guys:
|> Enum.flat_map(fn {_, %{metas: metas}} -> metas end)

—edit—
For the second example, I came up with this (which does indeed make me happier):

|> Map.values
|> Enum.flat_map(&Map.get(&1, :metas))
1 Like
:maps.map(fn _k, v -> length(v) end, m)

maybe not idiomatic, but erlang library comes in handy here.

1 Like

I might use for:

for {k, v} <- m, do: {k, length(v)}, into: %{}

2 Likes

What about skipping the first Enum.map?

Map.new(m, & {elem(&1, 0), String.length(elem(&1, 1))})

You can also use for with reduce.

for {k, v} <- m, reduce: %{} do acc -> Map.put(acc, k, String.length(v)) end

or Enum.reduce directly

Enum.reduce(m, %{}, fn {k, v}, acc -> ... end)
7 Likes

Sure!

Enum.into(m, %{}, fn {k,v} -> {k,length(v)} end)
Enum.into(%{a: [1], b: [1,2]}, %{}, fn {k,l} -> {k,length(l)} end)
1 Like

Actually Map.new/2 accepts a function, so you can also directly call Map.new(m, fn {k,v}-> {k, length(v)} end).

12 Likes

It took 5 replies to get the most straight solution for such a simple question. :smiley: I believe normally people would write what mrkurt already wrote which is not “not concise” at all.

You might not have seen I used Map.new/2 in my reply :slight_smile:

5 Likes

Oh sorry I missed it!

Also worth noting, Map.new/2 should be the fastest option as well according to this benchmark.

3 Likes

and

Oh my gosh these are great. That Map.new/2 function was exactly what I was looking for. My brain seems to handle the inline function better than complex capture.

2 Likes

Here’ s reply number 6.

Enum.into(m, %{}, fn {k,v}-> {k, length(v)} end)
2 Likes

This would have been my solution, though I would have put the into: before the do:

If this was immediately following a call to group_by/3, you can use frequencies/1 or frequencies_by/2 instead:


[:a, :a, :b, :b, :b, :c, :c, :c, :c]
|> Enum.group_by(& &1)
|> Map.new(fn {k, v} -> {k, length(v)} end)

[:a, :a, :b, :b, :b, :c, :c, :c, :c]
|> Enum.frequencies()

Result for both: %{a: 2, b: 3, c: 4}

1 Like