Elixir:在列表地图中找到最小长度列表的键

时间:2018-03-09 15:21:58

标签: elixir

在Elixir列表中查找与最小长度列表关联的密钥的有效方法是什么。我们说我有:

  const getKeyOfObject = (key) => markers.map(item => 
  item.coords[key]).join('')

  getKeyOfObject('lat') // 14.5858544

我知道我能做到:

z = %{a: [1, 2, 3], b: [4, 5], c: [6, 7, 8, 9]}

会给我:

Enum.map z, fn {k, v} -> length(v) end

但我真正需要的只是答案,即与最小值2相关的关键,当然是:b。

我将在动态地图列表中大约每秒运行一次,所以我希望它尽可能高效。

4 个答案:

答案 0 :(得分:3)

除非绝对必要,否则我更喜欢简单而不是性能,所以如果性能不那么令我担心,我会如何做到这一点:

map
|> Enum.min_by(fn {_k, v} -> length(v) end)
|> elem(0)

通过@Dogbert添加基准测试显示,平均速度慢了约1.5倍。在我的机器上90μs/迭代。这仍然是每秒运行它的充足空间。

答案 1 :(得分:2)

如果您正在寻找速度,:maps.fold通常会比Enum.reduce快一点。这是一个比@ mudasobwa实施快〜10%的实现:

:maps.fold(
  fn k, v, {_, min} = acc ->
    l = length(v)
    if min == 0 || l < min, do: {k, l}, else: acc
  end,
  {nil, 0},
  map
)
|> elem(0)

基准代码:

map = for(key <- 1..1000, into: %{}, do: {key, Enum.random([[1, 2, 3], [4, 5], [6, 7, 8, 9]])})

Benchee.run(%{
  "mudasobwa" => fn ->
    Enum.reduce(map, {nil, 0}, fn
      {k, v}, {_, len} when len == 0 or len > length(v) -> {k, length(v)}
      _, acc -> acc
    end)
    |> elem(0)
  end,
  "dogbert" => fn ->
    :maps.fold(
      fn k, v, {_, min} = acc ->
        l = length(v)
        if min == 0 || l < min, do: {k, l}, else: acc
      end,
      {nil, 0},
      map
    )
    |> elem(0)
  end
})

输出:

Name                ips        average  deviation         median         99th %
dogbert         12.36 K       80.90 μs    ±25.05%          73 μs      146.20 μs
mudasobwa       11.14 K       89.81 μs    ±34.32%          80 μs         185 μs

Comparison:
dogbert         12.36 K
mudasobwa       11.14 K - 1.11x slower

答案 2 :(得分:1)

在Elixir中执行任何操作的最有效方法是Enum.reduce/3

Enum.reduce(z, {nil, 0}, fn
  {k, v}, {_, len} when len == 0 or len > length(v) -> {k, length(v)}
   _, acc -> acc
end)

#⇒ {:b, 2}

要仅获取:b,模式匹配结果:

{key, _} = ⇑the above⇑

或(更糟)管道|> Tuple.to_list() |> List.first

这里我们更新累加器如果:

  • 我们刚刚开始,保存的值为空,或
  • 新长度小于旧长度。

答案 3 :(得分:0)

对于任何想重复@ Dogbert基准测试的人,包括Patrick Oscity的回答:

map = for(key <- 1..1000, into: %{}, do: {key, Enum.random([[1, 2, 3], [4, 5], [6, 7, 8, 9]])})

Benchee.run(%{
  "mudasobwa" => fn ->
    Enum.reduce(map, {nil, 0}, fn
      {k, v}, {_, len} when len == 0 or len > length(v) -> {k, length(v)}
      _, acc -> acc
    end)
    |> elem(0)
  end,
  "dogbert" => fn ->
    :maps.fold(
      fn k, v, {_, min} = acc ->
        l = length(v)
        if min == 0 || l < min, do: {k, l}, else: acc
      end,
      {nil, 0},
      map
    )
    |> elem(0)
    end,
  "oscity" => fn ->
        map
        |> Enum.min_by(fn {_k, v} -> length(v) end)
        |> elem(0)
    end
})

我在Nvidia TX2(64位ARM)上的结果:

Name                ips        average  deviation         median         99th %

    oscity           252.78        3.96 ms     ±3.01%        3.92 ms        4.38 ms
    mudasobwa         80.66       12.40 ms     ±1.61%       12.37 ms       13.15 ms
    dogbert           32.90       30.39 ms     ±0.90%       30.32 ms       31.27 ms

Benchee代码在这里:

https://hex.pm/packages/benchee

即添加 {:benchee, "~> 0.12.1"} 在mix.exs的deps下。