Reduce array that every element can have an array

I have an array that every element in it can have 2 fields that contains array too
like that

 [
      %{name: "a1", ar1: [%{name: "aa1"}, %{name: "aa2"}, %{name: "aa3"}], ar2: [%{department: "a23"}]},
      %{name: "a2", ar1: [%{name: "a22"}, %{name: "a32"}], ar2: [%{department: "a23"}, %{department: "a25"}]}
    ]

I want that the result will be:

[
%{name: "a1",  ar1: %{name: "aa1"}, ar2: %{department: "a23"}},
%{name: "a1",  ar1: %{name: "aa2"}, ar2: nil},
%{name: "a2",  ar1: %{name: "a22"}, ar2: %{department: "a23"}},
%{name: "a2",  ar1: %{name: "a32"}, ar2: %{department: "a25"}}
]

How do I do that?
Any suggestions?

What did you try so far?

%{"a1", ar1: %{name: "aa1"}, ar2: %{department: "a23"}} This is not a valid map. I guesst it’s missing the key :name for value "a1".

Just fix the results array

would be easy if we had something like python’s zip-longest:

>>> itertools.zip_longest([1,2,3], ["a", "b"])
[(1, 'a'), (2, 'b'), (3, None)]

Enum.zip will lose elements in the smaller list:

Enum.zip([%{name: "aa1"}, %{name: "aa2"}, %{name: "aa3"}], [%{department: "a23"}])
[{%{name: "aa1"}, %{department: "a23"}}]

you could pad the smaller list with nils.

You could write your own zip-longest.

defmodule Foo do
  def zip(a, b, c \\ [])

  def zip([], [], c), do: Enum.reverse(c)

  def zip([ha|ta], [], c) do
    zip(ta, [], [{ha, nil}|c])
  end

  def zip([], [hb|tb], c) do
    zip([], tb, [{hb, nil}|c])
  end

  def zip([ha|ta], [hb|tb], c) do
    zip(ta, tb, [{ha, hb}|c])
  end

  def transform(list) do
    Enum.flat_map(list, fn i ->
      i.ar1 |> zip(i.ar2) |> Enum.map(fn {ar1, ar2} ->
        %{name: i.name, ar1: ar1, ar2: ar2}
      end)
    end)
  end
end
iex(15)> a
[
  %{
    ar1: [%{name: "aa1"}, %{name: "aa2"}, %{name: "aa3"}],
    ar2: [%{department: "a23"}],
    name: "a1"
  },
  %{
    ar1: [%{name: "a22"}, %{name: "a32"}],
    ar2: [%{department: "a23"}, %{department: "a25"}],
    name: "a2"
  }
]
iex(16)> Foo.transform(a)
[
  %{ar1: %{name: "aa1"}, ar2: %{department: "a23"}, name: "a1"},
  %{ar1: %{name: "aa2"}, ar2: nil, name: "a1"},
  %{ar1: %{name: "aa3"}, ar2: nil, name: "a1"},
  %{ar1: %{name: "a22"}, ar2: %{department: "a23"}, name: "a2"},
  %{ar1: %{name: "a32"}, ar2: %{department: "a25"}, name: "a2"}
]
1 Like

just found out why thats not in Enum

I don’t think there is a function for it right now. I would love to add something like zip_all or zip_with_padding or simply zip/3 (if we are copying chunk signature). The tricky part is exactly in finding a name.

José Valim, 19 Sept 2015, 11:56:32

https://groups.google.com/g/elixir-lang-talk/c/pil9caRXQnM/m/bPnKaZhUAAAJ

2 Likes

It looks like what you would need is a combination of Enum.flat_map, Enum.zip, and maybe Enum.map.

To be honest, your example data made it a bit hard to follow what exactly you’re trying to do because it’s very abstract. Can you use more concrete names for the keys?

Also, if I’m guessing correctly about what you’re trying to do, the example result set is missing an entry for the third a1 key.

you mean like so: Reduce array that every element can have an array - #5 by Marcus :wink:

But I agree, if that data structure (in+out) is not forced on you, I’d think about how to improve them.

1 Like

Thanks you all. I found @Marcus solution just what I need
Thanks