首页 > 解决方案 > 在 Elixir 中展平嵌套地图列表

问题描述

我正在尝试展平嵌套地图列表,以便输出是地图列表,然后可以将其插入数据库表中。嵌套地图可以包含地图列表。一般来说,嵌套地图的最小示例是这样的:

  %{
    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,这些键不是必需的,因此被删除。

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

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

谢谢!

标签: functional-programmingelixir

解决方案


因为当我们遇到一个列表作为一个值时,我们需要从字面上分叉遍历这个级别,所以我们最好的朋友是Task.async_stream/3. 一旦我们要懒惰地执行它,所有的内部操作也是懒惰的,直到我们需要终止结果以从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"}
#  ]

这是一篇博文,以“狼、山羊、卷心菜”之谜为例详细解释了这种技术。


推荐阅读