I wish efficiently to transpose a (large) list of lists in Elixir, where each sublist has the same number of scalar elements. So from this:
iex(1)> x = [[1, 2], [3, 4], [5, 6]]
[[1, 2], [3, 4], [5, 6]]
I want to get to this:
[[1, 3, 5], [2, 4, 6]]
In Python I would use the "splat" operator *
with zip
:
In [1]: x = [[1, 2], [3, 4], [5, 6]]
In [2]: x
Out[2]: [[1, 2], [3, 4], [5, 6]]
In [3]: zip(*x)
Out[3]: [(1, 3, 5), (2, 4, 6)]
This is exactly equivalent to tranposing a matrix.
What is the equivalent in Elixir? I need to do this for large lists of lists coming from CSVs.
Bonus: similar for maps eg:
iex(1)> %{"a1" => %{"d1" => 1, "d2" => 2}, "a2" => %{"d1" => 3, "d2" => 4}, "a3" => %{"d1" => 5, "d2" => 6}}
%{
"a1" => %{"d1" => 1, "d2" => 2},
"a2" => %{"d1" => 3, "d2" => 4},
"a3" => %{"d1" => 5, "d2" => 6}
}
becomes
%{
"d1" => %{"a1" => 1, "a2" => 3, "a3" => 5},
"d2" => %{"a1" => 2, "a2" => 4, "a3" => 6}
}
For lists you have Enum.zip/2
and Stream.zip/1
iex> Enum.zip([[1, 2], [3, 4], [5, 6]])
[{1, 3, 5}, {2, 4, 6}]
Rearranging the map gets a little more tricky. I'm sure there's a much cleverer, better way of doing it, but you could break it into a flat enumerable then form it back into a map. And here, I would look at using a comprehension.
iex> input = %{
...> "a1" => %{"d1" => 1, "d2" => 2},
...> "a2" => %{"d1" => 3, "d2" => 4},
...> "a3" => %{"d1" => 5, "d2" => 6}
...> }
iex> for {outer, map} <- input, {inner, value} <- map, reduce: %{} do
...> acc -> Map.update(acc, inner, %{outer => value}, &Map.put(&1, outer, value))
...> end
%{
"d1" => %{"a1" => 1, "a2" => 3, "a3" => 5},
"d2" => %{"a1" => 2, "a2" => 4, "a3" => 6}
}
For lists:
As BrettBeatty mentioned, you can use zip/1 as follows:
iex(1)> x = [[1, 2], [3, 4], [5, 6]]
iex(2)> x |> Enum.zip |> Enum.map(&(Tuple.to_list(&1)))
There is also a recursive way to do it as well:
defmodule E do
def transpose([]), do: []
def transpose([[]|_]), do: []
def transpose(list) do
[Enum.map(list, &hd/1) | transpose(Enum.map(list, &tl/1))]
end
end
iex(3)> x |> E.transpose()
Brett's answer is absolutely correct; I just wanted to share another approach for transposing maps using Kernel.put_in/3
with Access.key/2
. This approach is easily extendable to transpose maps of any depth.
Enum.reduce(map, %{}, fn {k, vs}, acc ->
Enum.reduce(vs, acc, fn {ik, iv}, acc ->
put_in(acc, [Access.key(ik, %{}), k], iv)
end)
end)
#⇒ %{
# "d1" => %{"a1" => 1, "a2" => 3, "a3" => 5},
# "d2" => %{"a1" => 2, "a2" => 4, "a3" => 6}
# }
Access.key/2
accepts a default value, allowing you to bury an element as deep as needed.
put_in(%{}, [
Access.key(:k1, %{}),
Access.key(:k2, %{}),
Access.key(:k3, %{})
], 42)
#⇒ %{k1: %{k2: %{k3: 42}}}
Access
-based solution for transposing the array is also possible, but there is a need to know the length of the resulting list upfront.
len = list |> Enum.map(&Enum.count/1) |> Enum.max()
Enum.map(0..len-1, fn idx ->
Enum.map(list, &get_in(&1, [Access.at(idx)]))
end)
#⇒ [[1, 3, 5], [2, 4, 6]]
Unlike Enum.zip/1
, it'll preserve all the elements, putting nil
s where the input is shorter than max length.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.