展平 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 键)。您还会注意到嵌套发生的地方,ce,这些键不是必需的,因此被删除。

我试图递归映射键,但我 运行 遇到的问题是输出始终是父映射中键数的长度。

任何有关如何解决此问题的帮助或想法将不胜感激。

谢谢!

因为当我们遇到一个列表作为值时,我们需要从字面上 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.

的例子中详细解释了这项技术