How to convert to this map

Hello friends
I need to convert this

[
  %{one: 1, two: 11},
  %{one: 2, two: 22},
  %{one: 3, two: 33},
  %{one: 4, two: 44}
]

to

%{
  one: [1,2,3,4],
  two: [11,22,33,44]
}

And vice versa means turning the second part into the first

Thanks

2 Likes

Hey @edxofp what have you tried so far?

6 Likes

I tried many ways but I do not have enough knowledge in Elixir and map, unfortunately I could not solve it

posted prematurely, editing…

You can solve with with:

  • one call to Enum.reduce/3
  • and the syntax for updating maps %{map | key: :updated_value}

and optionally Enum.reverse if you care about the order of the elements in the list.

See what you can achieve and share it with us.

4 Likes

There’s also Enum.group_by =P I leave reading the docs as a continued exercise

Edit: never mind, it’s doable, but hard.

1 Like

Assuming all maps in the list will have same keys and the list of maps being x and map of lists being y:

You can convert x to y with-

x 
|> Enum.reduce(%{}, fn x, acc -> Map.merge(x, acc, fn _, a, b -> is_list(b) && [a | b] || [a | [b]] end) end)
|> Map.map(fn {_, value} -> Enum.reverse(value) end)

and you can convert y to x with-

{k,vs} = y |> then(&{Map.keys(&1), Map.values(&1) |> Enum.zip()})
Enum.reduce(vs, [], fn x, acc -> [Map.new(Enum.zip(k, Tuple.to_list(x))) | acc] end)

I did it in a hurry so I could be wrong, but in general, reduce is your friend in cases like these :slight_smile:

Edit:

What if your list was: [%{x: 0, y: 0, z: 0}, %{x, 0, y: 0}, %{z: 0}, %{p: 10, q: 10}] ? How would you like the solutions to be then? In the solution I gave above, it would work best if you have uniform data structure. But with some tweak at the reverse function (i.e. maybe Map.map(fn {_, value} -> is_list(value) && Enum.reverse(value) || [value] end)) you could achieve the desired result but the inverse of it could need some more love. Let me know if you can get something around those?

Maybe there are some more appropriate Enum.* function out there that could do it faster but this is (or something around this) how I worked on transforming CSV file at work long ago (your vice-versa part).

Also, I really, really wish iex had multiline history enabled :slight_smile:

1 Like

I haven’t had a chance of trying it yet but maybe you will: Improved? IEx shell history. My pet project/hack session

1 Like

I am going to. Though I know LiveBook would solve this for me, but I spend an unhealthy amount of time inside iex so I am very much grateful to you for suggesting this (hopefully) gem. Thank you.

This is great, thank you

My solution for a fixed set of keys.

list = [
  %{one: 1, two: 11},
  %{one: 2, two: 22},
  %{one: 3, two: 33},
  %{one: 4, two: 44}
]

Enum.reduce(Enum.reverse(list), %{one: [], two: []}, fn %{one: one, two: two}, acc ->
  %{acc | one: [one | acc.one], two: [two | acc.two]}
end)

and for any given set of keys:

list = [
  %{x: 0, y: 0, z: 0},
  %{x: 0, y: 0},
  %{z: 0},
  %{p: 10, q: 10},
  %{one: 1, two: 11},
  %{one: 2, two: 22},
  %{one: 3, two: 33},
  %{one: 4, two: 44}
]

for map <- Enum.reverse(list), {key, value} <- map, reduce: %{} do
  acc ->
    Map.update(acc, key, [value], &[value | &1])
end

for the second solution, you can use Enum.reduce instead of for/reduce

3 Likes

This is so much cleaner than mine👌

2 Likes

another one

m = [
  %{one: 1, two: 11},
  %{one: 2, two: 22},
  %{one: 3, two: 33},
  %{one: 4, two: 44}
]
m |> Enum.flat_map(fn map -> map end) |> Enum.group_by(fn {k, _v} -> k end, fn {_k, v} -> v end) 
5 Likes

BEAUTIFUL SOLUTION!

Wow… this is even simpler. This is the way to go, forget about for/Enum.reduce.

this is probably what you @ityonemo had in mind.

If I may rewrite in a bit more idiomatic/concise way,

m
|> Enum.flat_map(&Function.identity/1)
|> Enum.group_by(&elem(&1, 0), &elem(&1, 1))

Thank you @rhruiz

3 Likes

I’m really happy to be in elixir community
It is good that you are introducing the available ways

Thank you all

2 Likes

I happy you asked this question. Gave me an idea for my blog for next year :slight_smile:

1 Like