展平 Elixir 中的嵌套地图列表
Flatten a list of nested maps in Elixir
我正在尝试展平嵌套地图列表,以便输出是地图列表,然后可以将其插入数据库 table。嵌套地图可以包含地图列表。通常,嵌套映射的最小示例如下所示:
%{
a: "a",
b: "b",
c: [
%{
d: "d1",
e: [%{f: "f1", g: "g1"}],
h: "h1",
i: "i1"
},
%{
d: "d2",
e: [%{f: "f2", g: "g2"}],
h: "h2",
i: "i2"
}
]
}
我要寻找的输出是:
[
%{f: "f1", g: "g1", d: "d1", h: "h1", i: "i1", b: "b", a: "a"},
%{f: "f2", g: "g2", d: "d2", h: "h2", i: "i2", b: "b", a: "a"}
]
列表的长度等于“终端”映射的数量(即本例中的 f
键)。您还会注意到嵌套发生的地方,c
和 e
,这些键不是必需的,因此被删除。
我试图递归映射键,但我 运行 遇到的问题是输出始终是父映射中键数的长度。
任何有关如何解决此问题的帮助或想法将不胜感激。
谢谢!
因为当我们遇到一个列表作为值时,我们需要从字面上 fork 遍历关卡,所以我们最好的朋友是 Task.async_stream/3
。一旦我们要懒惰地执行它,所有的内部操作也是懒惰的,直到我们需要终止结果到 return 它从 flatten/1
(with Enum.to_list/1
.)
defmodule Flat do
@spec flatten(map()) :: [map()]
def flatten(input),
do: input |> do_flatten([%{}]) |> Enum.to_list()
@doc "here we fork it in parallel and collect results"
defp do_flatten([%{}|_] = input, acc) do
input
|> Task.async_stream(&do_flatten(&1, acc))
|> Stream.flat_map(&elem(&1, 1))
end
@doc """
add `{key, value}` pairs to each list
in currently accumulated result
"""
defp do_flatten(%{} = input, acc) do
Stream.flat_map(acc, fn list ->
Enum.reduce(input, [list], &do_flatten(&1, &2))
end)
end
@doc "enumerable as value → go for it"
defp do_flatten({_k, v}, acc) when is_list(v) or is_map(v),
do: do_flatten(v, acc)
@doc "the leaf, add to all lists in the accumulator"
defp do_flatten({k, v}, acc),
do: Stream.map(acc, &Map.put(&1, k, v))
end
input = %{
a: "a", b: "b",
c: [
%{d: "d1", e: [%{f: "f1", g: "g1"}], h: "h1", i: "i1"},
%{d: "d2", e: [%{f: "f2", g: "g2"}], h: "h2", i: "i2"}]
}
Flat.flatten()
#⇒ [
# %{a: "a", b: "b", d: "d1", f: "f1", g: "g1", h: "h1", i: "i1"},
# %{a: "a", b: "b", d: "d2", f: "f2", g: "g2", h: "h2", i: "i2"}
# ]
这里有一篇博客 post 在 “Wolf, Goat, Cabbage” riddle.
的例子中详细解释了这项技术
我正在尝试展平嵌套地图列表,以便输出是地图列表,然后可以将其插入数据库 table。嵌套地图可以包含地图列表。通常,嵌套映射的最小示例如下所示:
%{
a: "a",
b: "b",
c: [
%{
d: "d1",
e: [%{f: "f1", g: "g1"}],
h: "h1",
i: "i1"
},
%{
d: "d2",
e: [%{f: "f2", g: "g2"}],
h: "h2",
i: "i2"
}
]
}
我要寻找的输出是:
[
%{f: "f1", g: "g1", d: "d1", h: "h1", i: "i1", b: "b", a: "a"},
%{f: "f2", g: "g2", d: "d2", h: "h2", i: "i2", b: "b", a: "a"}
]
列表的长度等于“终端”映射的数量(即本例中的 f
键)。您还会注意到嵌套发生的地方,c
和 e
,这些键不是必需的,因此被删除。
我试图递归映射键,但我 运行 遇到的问题是输出始终是父映射中键数的长度。
任何有关如何解决此问题的帮助或想法将不胜感激。
谢谢!
因为当我们遇到一个列表作为值时,我们需要从字面上 fork 遍历关卡,所以我们最好的朋友是 Task.async_stream/3
。一旦我们要懒惰地执行它,所有的内部操作也是懒惰的,直到我们需要终止结果到 return 它从 flatten/1
(with Enum.to_list/1
.)
defmodule Flat do
@spec flatten(map()) :: [map()]
def flatten(input),
do: input |> do_flatten([%{}]) |> Enum.to_list()
@doc "here we fork it in parallel and collect results"
defp do_flatten([%{}|_] = input, acc) do
input
|> Task.async_stream(&do_flatten(&1, acc))
|> Stream.flat_map(&elem(&1, 1))
end
@doc """
add `{key, value}` pairs to each list
in currently accumulated result
"""
defp do_flatten(%{} = input, acc) do
Stream.flat_map(acc, fn list ->
Enum.reduce(input, [list], &do_flatten(&1, &2))
end)
end
@doc "enumerable as value → go for it"
defp do_flatten({_k, v}, acc) when is_list(v) or is_map(v),
do: do_flatten(v, acc)
@doc "the leaf, add to all lists in the accumulator"
defp do_flatten({k, v}, acc),
do: Stream.map(acc, &Map.put(&1, k, v))
end
input = %{
a: "a", b: "b",
c: [
%{d: "d1", e: [%{f: "f1", g: "g1"}], h: "h1", i: "i1"},
%{d: "d2", e: [%{f: "f2", g: "g2"}], h: "h2", i: "i2"}]
}
Flat.flatten()
#⇒ [
# %{a: "a", b: "b", d: "d1", f: "f1", g: "g1", h: "h1", i: "i1"},
# %{a: "a", b: "b", d: "d2", f: "f2", g: "g2", h: "h2", i: "i2"}
# ]
这里有一篇博客 post 在 “Wolf, Goat, Cabbage” riddle.
的例子中详细解释了这项技术