Elixir - 如何深度合并地图?

asi*_*niy 11 elixir

随着Map.merge我有:

Map.merge(%{ a: %{ b: 1 }}, %{ a: %{ c: 3 }}) # => %{ a: %{ c: 3 }}
Run Code Online (Sandbox Code Playgroud)

但实际上我想:

Map.merge(%{ a: %{ b: 1 }}, %{ a: %{ c: 3 }}) # => %{ a: %{ b: 1, c: 3 }}
Run Code Online (Sandbox Code Playgroud)

有没有为这种情况编写递归样板函数的本机方法?

Pat*_*ity 26

正如@Dogbert建议的那样,你可以编写一个递归合并地图的函数.

defmodule MapUtils do
  def deep_merge(left, right) do
    Map.merge(left, right, &deep_resolve/3)
  end

  # Key exists in both maps, and both values are maps as well.
  # These can be merged recursively.
  defp deep_resolve(_key, left = %{}, right = %{}) do
    deep_merge(left, right)
  end

  # Key exists in both maps, but at least one of the values is
  # NOT a map. We fall back to standard merge behavior, preferring
  # the value on the right.
  defp deep_resolve(_key, _left, right) do
    right
  end
end
Run Code Online (Sandbox Code Playgroud)

以下是一些测试用例,可以让您了解如何解决冲突:

ExUnit.start

defmodule MapUtils.Test do
  use ExUnit.Case

  test 'one level of maps without conflict' do
    result = MapUtils.deep_merge(%{a: 1}, %{b: 2})
    assert result == %{a: 1, b: 2}
  end

  test 'two levels of maps without conflict' do
    result = MapUtils.deep_merge(%{a: %{b: 1}}, %{a: %{c: 3}})
    assert result == %{a: %{b: 1, c: 3}}
  end

  test 'three levels of maps without conflict' do
    result = MapUtils.deep_merge(%{a: %{b: %{c: 1}}}, %{a: %{b: %{d: 2}}})
    assert result == %{a: %{b: %{c: 1, d: 2}}}
  end

  test 'non-map value in left' do
    result = MapUtils.deep_merge(%{a: 1}, %{a: %{b: 2}})
    assert result == %{a: %{b:  2}}
  end

  test 'non-map value in right' do
    result = MapUtils.deep_merge(%{a: %{b: 1}}, %{a: 2})
    assert result == %{a: 2}
  end

  test 'non-map value in both' do
    result = MapUtils.deep_merge(%{a: 1}, %{a: 2})
    assert result == %{a: 2}
  end
end
Run Code Online (Sandbox Code Playgroud)


Dog*_*ert 11

如果在地图中只有1级地图嵌套,并且顶级地图的所有值都是地图,则可以使用Map.merge/3:

iex(1)> a = %{ a: %{ b: 1 }}
%{a: %{b: 1}}
iex(2)> b = %{ a: %{ c: 3 }}
%{a: %{c: 3}}
iex(3)> Map.merge(a, b, fn _, a, b -> Map.merge(a, b) end)
%{a: %{b: 1, c: 3}}
Run Code Online (Sandbox Code Playgroud)

对于无限嵌套,我相信编写函数是唯一的方法,但在该函数中,您可以使用它Map.merge/3来减少一些代码.