【问题标题】:Efficient way to filter a Map by value in Elixir在 Elixir 中按值过滤 Map 的有效方法
【发布时间】:2021-04-03 12:05:59
【问题描述】:

在 Elixir 中,根据值过滤 Map 的有效方法是什么。

现在我有以下解决方案

%{foo: "bar", biz: nil, baz: 4}
|> Enum.reject(fn {_, v} -> is_nil(v) end)
|> Map.new

这个解决方案对我来说似乎效率很低。当在 Map 上调用时,Enum.reject/2 返回 Keywords。由于我想要Map,我需要调用Map.new/1Keywords 转换回给我。

这似乎效率低下,因为Enum.reject/2 必须对Map 进行一次迭代,然后大概Map.new/1 必须对Keywords 进行另一次迭代。

什么是更有效的解决方案?

【问题讨论】:

    标签: dictionary filter elixir


    【解决方案1】:

    从 Elixir 1.13 版开始,Map.filter(map, fun) 原生存在。见https://hexdocs.pm/elixir/Map.html#filter/2

    【讨论】:

      【解决方案2】:

      你也可以这样写:

      m = %{foo: "bar", biz: nil, baz: 4}
      
      Enum.reduce(m, m, fn 
        {key, nil}, acc -> Map.delete(acc, key)
        {_, _}, acc -> acc
      end)
      

      如果m 中的nil 值很少,则上面的代码非常有效。

      【讨论】:

        【解决方案3】:

        在这种情况下,理解是一个好主意,因为它也不会创建中间列表并返回一个地图:

        map = %{baz: 4, biz: nil, foo: "bar"}
        for {key, value} <- map, !is_nil(value), into: %{}, do: {key, value}
        

        【讨论】:

        • 这不应该创建一个中间列表,但在实践中,我发现for ..., into: %{} 比使用Enum.filter/2 创建一个列表并将其传递给Map.new/1 还要慢。我用我在答案中发布的基准进行了尝试,这对我来说比 OP 的代码慢了大约 20-25%。
        • 谢谢 :) erlang 的解决方案是最快的解决方案,我并不感到惊讶。
        【解决方案4】:

        您可以使用:maps.filter/2,它会过滤地图并且不会创建任何中间列表:

        iex(1)> :maps.filter fn _, v -> v != nil end, %{foo: "bar", biz: nil, baz: 4}
        %{baz: 4, foo: "bar"}
        

        一个简单的基准测试证实这比Enum.filter + Map.new 更快:

        map = for i <- 1..100000, into: %{}, do: {i, Enum.random([nil, 1, 2])}
        
        IO.inspect :timer.tc(fn ->
          map
          |> Enum.reject(fn {_, v} -> is_nil(v) end)
          |> Map.new
        end)
        
        IO.inspect :timer.tc(fn ->
          :maps.filter fn _, v -> v != nil end, map
        end)
        
        {44728,
         %{48585 => 1, 60829 => 2, 12995 => 1, 462 => 2, 704 => 2, 28954 => 2,
           29635 => 2, 78798 => 1, 92572 => 1, 89750 => 2, 39389 => 2, 62855 => 2,
           79313 => 1, 92062 => 2, 61871 => 1, 92856 => 2, 75920 => 1, 59922 => 1,
           37912 => 2, 30420 => 2, 51211 => 2, 7994 => 2, 78269 => 2, 9765 => 2,
           38352 => 2, 6653 => 1, 82555 => 2, 54031 => 2, 45138 => 1, 41351 => 1,
           40746 => 1, 5961 => 1, 66704 => 2, 33823 => 1, 47603 => 1, 86873 => 1,
           81009 => 2, 96255 => 1, 36219 => 1, 1328 => 2, 33314 => 1, 54477 => 2,
           40189 => 2, 27028 => 1, 31676 => 1, 94037 => 1, 32388 => 1, 4351 => 1,
           46309 => 1, ...}}
        {28638,
         %{48585 => 1, 60829 => 2, 12995 => 1, 462 => 2, 704 => 2, 28954 => 2,
           29635 => 2, 78798 => 1, 92572 => 1, 89750 => 2, 39389 => 2, 62855 => 2,
           79313 => 1, 92062 => 2, 61871 => 1, 92856 => 2, 75920 => 1, 59922 => 1,
           37912 => 2, 30420 => 2, 51211 => 2, 7994 => 2, 78269 => 2, 9765 => 2,
           38352 => 2, 6653 => 1, 82555 => 2, 54031 => 2, 45138 => 1, 41351 => 1,
           40746 => 1, 5961 => 1, 66704 => 2, 33823 => 1, 47603 => 1, 86873 => 1,
           81009 => 2, 96255 => 1, 36219 => 1, 1328 => 2, 33314 => 1, 54477 => 2,
           40189 => 2, 27028 => 1, 31676 => 1, 94037 => 1, 32388 => 1, 4351 => 1,
           46309 => 1, ...}}
        

        【讨论】:

        【解决方案5】:

        它可能有点贵,但它更具声明性,IMO 增加了更多价值。 还要考虑您的收藏有多大,以及优化此过滤器是否有意义。

        不过,我理解你的担心,所以我做了以下事情:

        %{foo: "bar", biz: nil, baz: 4}
        |> Enum.reduce(%{}, filter_nil_values/2)
        

        filter_nil_values/2 定义为

        defp filter_nil_values({_k, nil}, accum), do: accum
        defp filter_nil_values({k, v}, accum), do: Map.put(accum, k, v)
        

        我尝试在单行函数中执行此操作,但看起来很糟糕。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2023-03-16
          • 2010-09-10
          • 1970-01-01
          • 2017-09-29
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多