在 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/1
把那个Keywords
转换回给我。
这似乎效率低下,因为
Enum.reject/2
必须迭代Map
一次然后大概,Map.new/1
必须再次迭代Keywords
。
什么是更有效的解决方案?
您可以使用
: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, ...}}
在这种情况下,理解将是一个好主意,因为它也不会创建中间列表并返回给您一张地图:
map = %{baz: 4, biz: nil, foo: "bar"}
for {key, value} <- map, !is_nil(value), into: %{}, do: {key, value}
它可能有点贵,但更具声明性,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)
我试图在一个单行函数中做到这一点,但它看起来很糟糕。
也可以这样写:
m = %{foo: "bar", biz: nil, baz: 4}
Enum.reduce(m, m, fn
{key, nil}, acc -> Map.delete(acc, key)
{_, _}, acc -> acc
end)
如果
nil
中的m
值很少,上面的代码非常有效。
Map.filter(%{foo: "bar", biz: nil, baz: 4},
fn {_, v} -> v != nil end)