从地图创建路径

Creating paths from a map

我遇到了这样的问题,不知道如何正确解决。我需要创建给定地图的路径列表。路径列表应由元组组成,包括指向给定值的键列表和值本身,如下例所示。

示例输入:

%{
  foo: %{
    bar: %{
      value: "y"
    },
    car: %{
      value: "x"
    }
  },
  second: "level"
}

示例输出:

[
 { [:foo, :bar, :value], "y" },
 { [:foo, :car, :value], "x" },
 { [:second], "level"}
]

我尝试实现它的方法是使用 Enum.reduce()/3 的递归函数,遍历映射中的所有键和值。

def reduce_map(paths_map, nest) do
  Enum.reduce(paths_map, nest, fn {key, val}, acc ->
    case typeof(val) do
     "map" ->
        acc ++ [reduce_map(val, [key])]
      _ ->
        acc ++ [key]
    end
  end)
end

我觉得这是解决这个问题的正确方向,但我仍然无法弄清楚如何从这一点进一步前进。上面给出的这个函数的输出是:

[[:foo, [:bar, :value], [:car, :value]], :second]

希望有人能帮我解决这个问题:)干杯

你的方向确实是对的。但是请注意,您将 val 传递给递归函数调用,但当 val 不是映射时,您不会将其包含在结果中。

此外,为了避免嵌套键被分组在父级下,就像它似乎已经发生在你的结果中一样,你可能会使用一个额外的参数来跟踪路径中的键直到某个键在嵌套地图中。

这是一个使用我刚才提到的方法的解决方案,但起草速度非常快,我相信它相当幼稚,可以通过多种方式进行改进:

defmodule Test do                                                
  def reduce(map, paths, accumulated_paths) do                    
    Enum.reduce(map, paths, fn {key, val}, acc when is_map(val) ->
        acc ++ [reduce(val, paths, accumulated_paths ++ [key])]   
      {key, val}, acc ->                                          
        acc ++ [{accumulated_paths ++ [key], val}]                
    end)
  end
end

然后在 iex 中:

iex> m = %{
...>   foo: %{
...>     bar: %{
...>       value: "y"
...>     },
...>     car: %{
...>       value: "x"
...>     }
...>   },
...>   second: "level"
...> }
%{foo: %{bar: %{value: "y"}, car: %{value: "x"}}, second: "level"}
iex> Test.reduce(m, [], []) |> List.flatten()
[{[:foo, :bar, :value], "y"}, {[:foo, :car, :value], "x"}, {[:second], "level"}]

如您所见,它产生了预期的结果,但是,例如,它需要调用 List.flatten 因为添加了嵌套列表

我编写了库来深度迭代嵌套结构,Iteraptor

如果您对第 3 方没问题,那就很简单:

Iteraptor.each(input, &IO.inspect/1)

{[:foo, :bar, :value], "y"}
{[:foo, :car, :value], "x"}
{[:second], "level"}

#⇒ %{foo: %{bar: %{value: "y"}, car: %{value: "x"}},
#    second: "level"}

它是 OSS,因此您可以查看源代码以获得更多见解。此外,它还提供 map/2reduce/3

当你开始嵌套时,我发现很难对 Enum.reduce 进行推理,并且通常最终只使用带有递归的基本函数来处理这样的事情:

defmodule Example do
  def reduce_map(tree) do
    tree |> reduce_map([]) |> List.flatten()
  end

  def reduce_map(tree, path) when is_map(tree) do
    for {k, v} <- tree, do: reduce_map(v, [k | path])
  end

  def reduce_map(value, path), do: {Enum.reverse(path), value}
end

IEx:

iex(1)> Example.reduce_map(%{foo: %{bar: %{value: "y"}, car: %{value: "x"}}, second: "level"})
[{[:foo, :bar, :value], "y"}, {[:foo, :car, :value], "x"}, {[:second], "level"}]