【问题标题】:How to partially compare two maps?如何部分比较两张地图?
【发布时间】:2022-01-30 13:00:22
【问题描述】:

我想比较两个映射的部分相等性,即仅针对某些键。例如我有两张地图:

map1 = %{a: 1, b: 2, c: 3}
map2 = %{a: 1, c: 3, d: 4}

我只想比较两个键的映射:a 和 :c,所以上面的例子应该返回 true。

我正在寻找一个简短的通用解决方案,因为我的地图中有很多键。

【问题讨论】:

  • 如果地图有任何共同的键,您是否希望解决方案返回true?或者他们是否有任何共同的键值对?
  • 如果两个映射的作用域内的键/值对相同,即忽略所有其他键/值对,则该函数应返回 true。

标签: elixir


【解决方案1】:

如果事先知道一组键,一个通用且简单的解决方案是使用Enum.all?/2检查所有键的条件:

map1 = %{a: 1, b: 2, c: 3}   
map2 = %{a: 1, c: 3, d: 4}
keys = [:a, :c]

Enum.all?(keys, fn key -> map1[key] == map2[key] end) # true

编辑:如果您需要动态检测重叠键,您可以执行类似的操作

def partial_equal?(map1, map2) when is_map(map1) and is_map(map2) do
  Enum.all?(map1, fn {key, value} ->
    case map2 do
      %{^key => another_value} when another_value != value -> false
      _ -> true
    end
  end)
end

# or, more compact
Enum.all?(map1, fn {key, value} -> not match?(%{^key => v} when v != value, map2) end)

它将检查来自map1 的所有键值,并在找到map2 中具有不同值的键时立即使用false 保释。

partial_equal?(%{a: 1, b: 2, c: 3},  %{a: 1, c: 3, d: 4})  # true
partial_equal?(%{a: 1, b: 2, c: 3},  %{a: 1, c: 0})  # false

【讨论】:

  • map[key] 通过Access,因此through :maps.find/2Map.get/2 不同,Map.get/2 使用pattern matching,据说更快。无论如何,我发现我使用Map.take/2 的解决方案更直接:)
  • 我同意Map.get 更有效,我犹豫了一下,但我优化了可读性。关于性能,与Map.take 相比,我的解决方案非常优化,因为它是只读的并且不依赖于构建新地图。 quick benchmark 表示 huge gap 的速度和内存使用率。相比之下,Access 的影响很小 :)
  • 关于直截了当,我也喜欢Map.take/2的版本,但我相信Enum.all?的意图也很清楚,读起来也很自然。
  • 是的,我完全同意。
【解决方案2】:

我会选择Map.take/2,这样会更惯用,并且能更好地阐明意图。

map1 = %{a: 1, b: 2, c: 3}
map2 = %{a: 1, c: 3, d: 4}

[map1, map2]
|> Enum.map(&Map.take(&1, [:a, :c]))
|> Enum.reduce(&Kernel.==/2)
#⇒ true

不过,对于大地图,@sabiwara 的解决方案效果更好,请参阅那里的 cmets。

【讨论】:

  • 我也想过这个,但我做了Map.take(map1, keys) == Map.take(map2, keys)。但是,我没有将其作为答案发布,因为此版本将生成两个子图的副本,如果有很多 keys,这可能是一个缺点。 Sabiwara 的版本没有复制任何内容,而是一次迭代完成。
  • 确实,问题中甚至提到了巨大的地图。我会在那里发出警告。
【解决方案3】:

我认为这个函数可以满足你的需要。

@doc """
Returns a boolean indicating if the given maps
have any key-value pairs in common.
"""
@spec have_any_key_value_pair_in_common?(map(), map()) :: boolean()
def have_any_key_value_pair_in_common?(map1, map2) do
  map_set1 = MapSet.new(map1)
  map_set2 = MapSet.new(map2)

  map_set1
  |> MapSet.intersection(map_set2)
  |> Enum.any?() 
end

我在这里所做的是将地图转换为“MapSets”,这就像数学集(每个元素都是唯一的)。当你这样做时,地图中的每个“元素”都是一个键值对,例如{:a, 1}.

然后我得到两个 MapSet 的“交集”。这将为我提供所有常见元素的列表,即两个映射共有的所有键值对。

有了这个列表,我只需要Enum.any? 得到一个布尔值(如果列表不为空则为真,如果为空则为假)。

【讨论】:

  • 这不会返回false,当它们确实有一个具有不同值的公共键时,因此拥有共同的键值对感觉与部分比较有点不同:have_any_key_value_pair_in_common?(%{a: 1, b: 2}, %{a: 1, b: 3}) == true。也许我们应该只取映射键之间的交集,然后比较它们?
  • @sabiwara 当它们“有一个具有不同值的公共键”时,它不会总是返回false。在您给出的示例中,它们具有共同的{:a, 1},因此它返回true。无论如何,我想我不明白你到底需要什么。
猜你喜欢
  • 2014-09-08
  • 1970-01-01
  • 2011-02-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多