【问题标题】:Erlang/Elixir Merging maps with mostly duplicate keysErlang/Elixir 合并具有大部分重复键的映射
【发布时间】:2017-04-21 07:08:28
【问题描述】:

假设我有两张地图 (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)
    ...
    

【问题讨论】:

  • 在您的特定应用程序中,是否可以提前知道哪些条目将被复制,或者将它们放在单独的数据结构中?

标签: dictionary erlang elixir


【解决方案1】:

我预计 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】:

    我会选择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
    

    【讨论】:

    • 是的,这是关于合并实现的更基本的问题,因为没有详细记录操作。它实际上可能永远不会成为此应用程序中的问题
    【解决方案3】:

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

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

    解决方案有一些细微差别:地图的比例对于决定地图在内存中的表示方式很重要——平面图适用于小型,散列图适用于大型。

    但是,选择简单的合并是因为哈希图用于优化很重要的大型地图。由于许多节点在大多数相似的地图中比较相等,因此该算法应该只需要重建地图下方树的一小部分。至少这是我们的研究表明的。这还没有在实践中测试过

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2010-11-06
      • 2023-03-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-07
      • 2016-12-25
      相关资源
      最近更新 更多