如何在 Elixir 中转置列表?
How do I tranpose a list in Elixir?
我希望在 Elixir 中有效地转置一个(大)列表列表,其中每个子列表具有相同数量的标量元素。所以从这个:
iex(1)> x = [[1, 2], [3, 4], [5, 6]]
[[1, 2], [3, 4], [5, 6]]
我想讲这个:
[[1, 3, 5], [2, 4, 6]]
在 Python 中,我会使用 "splat" 运算符 *
和 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)]
这完全等同于转置矩阵。
Elixir 中的等价物是什么?我需要对来自 CSV 的大量列表执行此操作。
奖励:地图类似,例如:
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}
}
变成
%{
"d1" => %{"a1" => 1, "a2" => 3, "a3" => 5},
"d2" => %{"a1" => 2, "a2" => 4, "a3" => 6}
}
对于列表,您有 Enum.zip/2
and Stream.zip/1
iex> Enum.zip([[1, 2], [3, 4], [5, 6]])
[{1, 3, 5}, {2, 4, 6}]
重新排列地图有点棘手。我敢肯定,有一种更聪明、更好的方法可以做到这一点,但您可以将其分解为平面可枚举,然后将其重新组合成地图。在这里,我会考虑使用理解。
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}
}
对于列表:
正如 BrettBeatty 提到的,您可以按如下方式使用 zip/1:
iex(1)> x = [[1, 2], [3, 4], [5, 6]]
iex(2)> x |> Enum.zip |> Enum.map(&(Tuple.to_list(&1)))
还有一种递归的方法:
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 的回答完全正确;我只是想分享另一种使用 Kernel.put_in/3
with Access.key/2
转置地图的方法。这种方法很容易可扩展到转置任何深度的地图。
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
接受默认值,允许您将元素埋入所需的深度。
put_in(%{}, [
Access.key(:k1, %{}),
Access.key(:k2, %{}),
Access.key(:k3, %{})
], 42)
#⇒ %{k1: %{k2: %{k3: 42}}}
基于 Access
的数组转置解决方案也是可能的,但是需要预先知道结果列表的长度。
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]]
与 Enum.zip/1
不同,它将保留所有元素,将 nil
放在输入短于最大长度的位置。
我希望在 Elixir 中有效地转置一个(大)列表列表,其中每个子列表具有相同数量的标量元素。所以从这个:
iex(1)> x = [[1, 2], [3, 4], [5, 6]]
[[1, 2], [3, 4], [5, 6]]
我想讲这个:
[[1, 3, 5], [2, 4, 6]]
在 Python 中,我会使用 "splat" 运算符 *
和 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)]
这完全等同于转置矩阵。
Elixir 中的等价物是什么?我需要对来自 CSV 的大量列表执行此操作。
奖励:地图类似,例如:
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}
}
变成
%{
"d1" => %{"a1" => 1, "a2" => 3, "a3" => 5},
"d2" => %{"a1" => 2, "a2" => 4, "a3" => 6}
}
对于列表,您有 Enum.zip/2
and Stream.zip/1
iex> Enum.zip([[1, 2], [3, 4], [5, 6]])
[{1, 3, 5}, {2, 4, 6}]
重新排列地图有点棘手。我敢肯定,有一种更聪明、更好的方法可以做到这一点,但您可以将其分解为平面可枚举,然后将其重新组合成地图。在这里,我会考虑使用理解。
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}
}
对于列表:
正如 BrettBeatty 提到的,您可以按如下方式使用 zip/1:
iex(1)> x = [[1, 2], [3, 4], [5, 6]]
iex(2)> x |> Enum.zip |> Enum.map(&(Tuple.to_list(&1)))
还有一种递归的方法:
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 的回答完全正确;我只是想分享另一种使用 Kernel.put_in/3
with Access.key/2
转置地图的方法。这种方法很容易可扩展到转置任何深度的地图。
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
接受默认值,允许您将元素埋入所需的深度。
put_in(%{}, [
Access.key(:k1, %{}),
Access.key(:k2, %{}),
Access.key(:k3, %{})
], 42)
#⇒ %{k1: %{k2: %{k3: 42}}}
基于
Access
的数组转置解决方案也是可能的,但是需要预先知道结果列表的长度。
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]]
与 Enum.zip/1
不同,它将保留所有元素,将 nil
放在输入短于最大长度的位置。