How to merge list pares into a single Map?

I have a list like this.

[ ["key1", 1], ["key2", 2], ["key3", 3] ]

Is there any way to convert the list into a single map like this?

%{key1: 1, key2: 2, key3: 3}
1 Like

A for comprehension works nicely here:

list = [ ["key1", 1], ["key2", 2], ["key3", 3] ]
for [k, v] <- list, into: %{}, do: { String.to_atom(k), v }
2 Likes

And also “classic” approach:

list |> Stream.map(fn [k, v] -> { String.to_atom(k), v} end) |> Enum.into(%{})

In this case comprehension is awesome, because it’s really concise and readable.

2 Likes

Depending of the source of the strings, I’d prefer to use String.to_existing_atom/1 to not waste strictly limited ressources.

5 Likes

Thank you for the quick replies!
I can convert it as I expected!

BTW, as the list was filtered with Enum.filter/2 beforehand, I don’t have to worry to waste the Atom tables, in fact.

2 Likes

An important difference between the solutions that @amarraja and @PatNowak presented, is that the Enum.map + Enum.into combination creates an intermediate list, while the for-comprehension does not.

3 Likes

I could use Stream :wink:

1 Like

That would not resolve the problem, only delay it:

list_of_kv_lists 
|> Enum.map(fn [k, v] -> { String.to_existing_atom(k), v} end) 
|> Enum.into(%{})

would first transform
[ ["key1", 1], ["key2", 2], ["key3", 3] ] into
[ {:key1, 1}, {:key2, 2}, {:key3, 3}] and this then into
%{key1: 1, key2: 2, key3: 3}.

list_of_kv_lists 
|> Stream.map(fn [k, v] -> { String.to_existing_atom(k), v} end) 
|> Stream.into(%{}) 
|> Stream.run

would not do anything until Stream.run is reached, and then take the first element [“key1”, 1], transform it into {:key1, 1}, insert this into the map, take the second element [“key2”, 2], transform it into {:key2, 2}, insert this into the map, etc.
This transformation step thus still is done separately.

If you want to do it ‘at once’, you can either use a for-comprehension or use the three-argument version of Enum.into:

list_of_kv_lists
|> Enum.into(%{}, fn [k, v] ->  { String.to_existing_atom(k), v} end)
1 Like

Why not use simply Stream.map and Enum.into? :slight_smile: I updated my answer above.

2 Likes

Because this would mean that you add the extra overhead of working with Streams for no extra purpose. If you want to be sure you can benchmark, but I am fairly certain that in this simple example, the version that does not use streams is significantly faster.

1 Like

This would be the “classic” approach I’d use. ^.^

iex> list = [ ["key1", 1], ["key2", 2], ["key3", 3] ]
[["key1", 1], ["key2", 2], ["key3", 3]]

iex> list |> Enum.reduce(%{}, fn ([k,v],acc) -> Map.put(acc,String.to_existing_atom(k),v) end) %{key1: 1, key2: 2, key3: 3}

And no intermediate list, no stream overhead, etc
 ^.^

1 Like