2017-02-15 8 views
1

Каков самый идиоматический способ выполнения операции карты на карте?Выполнение операции с картой на карте

два подхода приходят на ум:

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

Я думаю, что второй вариант не так уж плох. – JustMichael

+2

Существует также ': maps.map/2', который может быть более эффективным, чем любой из этих двух. – Dogbert

ответ

1

Я инстинктивно идти на Enum.map подход:

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

Это делает ваше намерение ясно, что это легко читать, он будет работать для всех Enum с и должно быть в порядке точки зрения производительности.

Когда речь заходит о производительности, Enum.map/2 ваш второй выбор после того, как :maps.map/2 предложил Dogbert:

##### 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 

Side Примечание: Там в Map.new/2, которая делает как создание карты и преобразование значений Enum в ключи карты и значения, так что вы можете иметь аналогичный подход без Enum.map.

+0

Карты меняют свою базовую структуру данных на 32 элемента. Было бы интересно увидеть контрольные показатели только под этим лимитом. –

+0

': maps.map' - это то, что я искал. Слишком плохо, что вы должны обернуть его вручную, чтобы использовать его в Elixir Pipeline. –

+1

О, и вы теряете возможность изменять ключ, если это необходимо. Тогда я, вероятно, займусь «Enum.map». Спасибо за отзывы и тесты! –

Смежные вопросы