在Elixir中按值过滤Map的有效方法

时间:2017-05-23 22:14:20

标签: dictionary filter elixir

在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

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

4 个答案:

答案 0 :(得分:7)

您可以使用: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, ...}}

答案 1 :(得分:1)

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

div {
  width: 500px;
  height: 280px;
  background: #1e90ff;
  -webkit-clip-path: polygon(100% 20%, 100% 40%, 0 40%, 15% 20%);
  clip-path: polygon(100% 20%, 100% 40%, 0 40%, 15% 20%);
  /* Center the demo */
  html,
  body {
    height: 100%;
  }
  body {
    display: flex;
    justify-content: center;
    align-items: center;
  }

答案 2 :(得分:0)

它可能有点贵,但更具声明性,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)

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

答案 3 :(得分:0)

您也可以这样写:

m = %{foo: "bar", biz: nil, baz: 4}

Enum.reduce(m, m, fn 
  {key, nil}, acc -> Map.delete(acc, key)
  {_, _}, acc -> acc
end)

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