Dictionary Elixir中按值过滤贴图的有效方法

Dictionary Elixir中按值过滤贴图的有效方法,dictionary,filter,elixir,Dictionary,Filter,Elixir,在Elixir中,通过值过滤映射的有效方法是什么 现在我有以下的解决方案 %{foo: "bar", biz: nil, baz: 4} |> Enum.reject(fn {_, v} -> is_nil(v) end) |> Map.new 这个解决方案在我看来效率很低。在映射上调用时,枚举拒绝/2返回关键字。因为我想要一张地图,所以我需要调用Map.new/1将关键字转换回我 这似乎效率低下,因为Enum.reject/2必须在Map上迭代一次,然后大概是Map。new

在Elixir中,通过值过滤
映射的有效方法是什么

现在我有以下的解决方案

%{foo: "bar", biz: nil, baz: 4}
|> Enum.reject(fn {_, v} -> is_nil(v) end)
|> Map.new
这个解决方案在我看来效率很低。在
映射上调用时,
枚举拒绝/2
返回
关键字
。因为我想要一张
地图
,所以我需要调用
Map.new/1
关键字
转换回我

这似乎效率低下,因为
Enum.reject/2
必须在
Map
上迭代一次,然后大概是
Map。new/1
必须在
关键字上迭代另一次


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

可能会有点贵,但它更具声明性,IMO会增加更多的价值。 还要考虑你的收藏有多大,如果优化这个过滤器是有意义的。

然而,我理解你的担忧,因此我所做的如下:

%{foo: "bar", biz: nil, baz: 4}
|> Enum.reduce(%{}, filter_nil_values/2)
其中
filter\u nil\u values/2
定义为

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

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

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

iex(1)> :maps.filter fn _, v -> v != nil end, %{foo: "bar", biz: nil, baz: 4}
%{baz: 4, foo: "bar"}
一个简单的基准测试证实了这比枚举过滤器更快

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)

在本例中,理解是一个好主意,因为它也不会创建中间列表并返回一个映射:

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

对于{key,value}也可以这样写:

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

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

如果
m

中几乎没有
nil
值,那么上面的代码是非常有效的。这不应该创建一个中间列表,但在实践中,我发现
for…,into:%{}
比使用
Enum.filter/2
创建一个列表并将其传递给
Map.new/1
还要慢。我用我在答案中发布的基准测试进行了测试,结果比OP的代码慢了20-25%。谢谢:)我并不惊讶erlang的解决方案是最快的解决方案。如果有人想知道,
:maps.filter
m = %{foo: "bar", biz: nil, baz: 4}

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