在地图上执行地图操作

时间:2017-02-15 21:34:27

标签: elixir

在地图上执行地图操作的最常用方法是什么?

我想到了两种方法:

Enum.reduce(%{a: 1, b: 2}, %{}, fn({k,v}, acc) -> Map.put(acc, k, v+1) end)
# => %{a: 2, b: 3}

for {k,v} <- %{a: 1, b: 2}, do: {k, v+1}, into: %{}
# => %{a: 2, b: 3}

有没有更好的选择,我没有想到?如果没有,上述哪一个更受欢迎?

1 个答案:

答案 0 :(得分:1)

我本能地采用Enum.map方法:

input
  |> Enum.map(fn{k, v} -> {k, v + 1} end)
  |> Map.new()

它使你的意图清晰,易于阅读,它适用于所有Enum,并且在性能方面应该是正常的。

在性能方面,Enum.map/2是您在Dogbert提出的:maps.map/2之后的第二选择:

##### With input Large   (100 0000 items) #####
Name                        ips        average  deviation         median
:maps.map                104.52        9.57 ms     ±9.91%        9.38 ms
Enum.map                  54.07       18.49 ms     ±8.32%       18.41 ms
Stream.map                44.33       22.56 ms    ±14.86%       22.50 ms
Enum.reduce               25.39       39.38 ms    ±22.03%       37.61 ms
for comprehension         25.01       39.99 ms    ±20.95%       37.30 ms

Comparison:
:maps.map                104.52
Enum.map                  54.07 - 1.93x slower
Stream.map                44.33 - 2.36x slower
Enum.reduce               25.39 - 4.12x slower
for comprehension         25.01 - 4.18x slower

不同方法的相对性能取决于输入映射的大小,但Enum.map始终是第二快的选项(至少在我的机器上)。

以下是基准测试的代码,使用Benchee

defmodule Mix.Tasks.Benchmark.MapMap do
  use Mix.Task

  def run(_args) do
    inputs = %{
      "Tiny    (10 items)"      => produce_map(10),
      "Small   (100 items)"      => produce_map(100),
      "Medium  (10 000 items)"     => produce_map(10_000),
      "Large   (100 0000 items)"    => produce_map(100_000),
    }

    Benchee.run(%{
      "Enum.reduce" =>
        fn(input) ->
          Enum.reduce(input, %{}, fn({k,v}, acc) -> Map.put(acc, k, mapper(v)) end)
        end,
      "for comprehension" =>
        fn(input) ->
          for {k,v} <- input, do: {k, mapper(v)}, into: %{}
        end,
      ":maps.map" =>
        fn(input) ->
          :maps.map(fn(_k, v) -> mapper(v) end, input)
        end,
      "Enum.map" =>
        fn(input) ->
          input
            |> Enum.map(fn{k, v} -> {k, mapper(v)} end)
            |> Map.new()
        end,
      "Stream.map" =>
        fn(input) ->
          input
            |> Stream.map(fn{k, v} -> {k, mapper(v)} end)
            |> Map.new()
        end
    }, [time: 1, warmup: 1, inputs: inputs])
  end

  def mapper(x), do: x + 1

  defp produce_atom(idx) do
    idx = Integer.to_string(idx)
    String.to_atom("a" <> idx)
  end

  defp produce_map(size) do
    1..size
      |> Enum.map(fn(i) -> {produce_atom(i), i} end)
      |> Map.new
  end
end

旁注:有Map.new/2,它既可以创建地图,也可以将枚举值转换为地图键和值,因此您可以采用类似的方法而不使用Enum.map

相关问题