2017-02-15 13 views
1

マップ上でマップ操作を実行する最も慣れ親しんだ方法は何ですか?マップ上でのマップ操作の実行

2つのアプローチが頭に浮かぶ:

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} 

は、私は考えていないことをより良い代替手段はありますか?そうでない場合は、上記のどちらが優先されますか?

+0

私は第2の選択肢がそれほど悪くないと思います。 – JustMichael

+2

':maps.map/2'もあり、これらの2つのどちらよりも効率的です。 – Dogbert

答えて

1

私は本能的にEnum.mapアプローチのために行くだろう:

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

それは、あなたの意図が明確になり、それがすべてのEnumのために働くだろうとOK性能面である必要があり、読みやすいです。それがパフォーマンスになると

Enum.map/2はDogbertによって提案:maps.map/2後に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のない同様のアプローチ。

+0

マップは、基本的なデータ構造を32要素で変更します。ベンチマークがその限界のすぐ下にあることは興味深いでしょう。 –

+0

':maps.map'は私が探していたものです。 Elixir Pipelineで手動で使用するには、それを手動でラップする必要があります。 –

+1

ああ、あなたが望むなら、鍵を変更する能力を失う。私はおそらく 'Enum.map'と一緒に行くでしょう。フィードバックとベンチマークをありがとう! –

関連する問題