Erlang / Elixir合并地图,主要是重复的密钥

时间:2017-04-20 22:24:56

标签: dictionary erlang elixir

假设我有两张地图( m1,m2 ),这些地图预计会有大多数相同的kv对,但每张地图可能都有其他不相同的条目。最终,我想要一张包含两张地图中所有kv对的地图,所以在高级别我想合并它们。

然而,考虑到Map.merge(Erlang BIF)和Map.split(尾递归)的实现,以及期望差异与地图大小成比例的启发式,哪个以下选项更适合达到预期的结果?

  1. 首先拆分以找到m2唯一的kv对,并仅合并那些

    ...
    {_duplicateKeys, m2only} = Map.split(m2, Map.keys(m1))
    Map.merge(m1, m2only)
    ...
    
  2. 或者只是合并,希望实现将优化构建地图

    ...
    Map.merge(m1, m2)
    ...
    

3 个答案:

答案 0 :(得分:3)

我会选择Map.merge方法。过早优化通常是反模式。如果您以后发现性能问题,则可以进行优化。 Erlang BIF通常非常高效。

编辑:

这是一个快速基准

spallen@Steves-MacBook-Pro ~/myprojects/elixir/maps  time ./map.exs                         
MapDemo running count: 5000000
./map.exs  21.30s user 2.73s system 98% cpu 24.371 total
spallen@Steves-MacBook-Pro  ~/myprojects/elixir/maps  time ./split.exs                       
SplitDemo running count: 5000000
./split.exs  25.68s user 4.28s system 98% cpu 30.479 total

这是代码

#! /usr/local/bin//elixir
defmodule MapDemo do
  @upper 5000000
  def run do
    IO.puts "MapDemo running count: #{@upper}"
    map1 =
      0..@upper
      |> Enum.map(& {"key_#{&1}", &1})
      |> Enum.into(%{})

    map2 =
      100..(@upper + 100)
      |> Enum.map(& {"key_#{&1}", &1})
      |> Enum.into(%{})

    Map.merge(map1, map2)
  end
end

MapDemo.run

#! /usr/local/bin//elixir
defmodule SplitDemo do
  @upper 5000000
  def run do
    IO.puts "SplitDemo running count: #{@upper}"
    map1 =
      0..@upper
      |> Enum.map(& {"key_#{&1}", &1})
      |> Enum.into(%{})

    map2 =
      100..(@upper + 100)
      |> Enum.map(& {"key_#{&1}", &1})
      |> Enum.into(%{})

    {_duplicateKeys, m2only} = Map.split(map2, Map.keys(map1))
    Map.merge(map1, m2only)
  end
end

SplitDemo.run

答案 1 :(得分:3)

我希望split的费用超过merge的任何费用。

当地图很大时,看起来BIF使用的是hashmap_merge,其中包含此注释:

/*
 * Strategy: Do depth-first traversal of both trees (at the same time)
 * and merge each pair of nodes.
 */

实现似乎检测到地图中何时存在相同的子树:

switch (sp->mix) {
    case 0: /* Nodes A and B contain the *EXACT* same sub-trees
               => fall through and reuse nodeA */

    case 1: /* Only unique A stuff => reuse nodeA */
        res = sp->nodeA;
        break;

    case 2: /* Only unique B stuff => reuse nodeB */
        res = sp->nodeB;
        break;

    case 3: /* We have a mix => must build new node */

答案 2 :(得分:2)

在查看Map.merge BIF的文档并在#elixir-lang中与asonge讨论后,决定简单合并是合适的解决方案。

...
Map.merge(m1, m2)
...

解决方案有一些细微差别:地图的比例对于决定地图在内存中的表示方式非常重要 - flatmap for small,hashmap for large。

然而,选择简单合并是因为散列图用于优化很重要的大型地图。由于许多节点在大多数相似的地图中将比较相等,因此算法应该仅需要重建地图下面的树的一小部分。至少这是我们研究所表明的;这在实践中尚未经过测试