2017-02-15 5 views
3

Какой будет элегантный, эффективный способ преобразования списка, например [1,2,3,4] на карту %{1=>2, 3=>4}? Я написал следующее:Elixir - Преобразование Список в карту

 Enum.reduce([1,2,3,4],%{}, fn(item, acc) -> 
     case Map.get(acc, :last) do 
      nil -> 
      Map.put(acc, :last, item) 
      last -> 
      acc = Map.put(acc, item, last) 
      Map.drop(acc, [:last]) 
     end 
     end) 

Но это не кажется очень изящным. Есть ли более элегантный и более чистый способ сделать это?

+1

Один важный вопрос: что вы хотите, когда в исходном списке есть нечетное количество элементов? Отбросить последнее значение или присвоить ему значение по умолчанию «nil», например? Со всеми решениями, использующими 'Enum.chunk', вы можете указать значения по умолчанию через' Enum.chunk (2, 2, [nil]) ', тогда как простой' Enum.chunk (2) 'будет отбрасывать куски, которые невозможно полностью заполнить , –

ответ

10

Вы можете использовать Enum.chunk/2:

[1, 2, 3, 4] 
    |> Enum.chunk(2) 
    |> Enum.map(fn [a, b] -> {a, b} end) 
    |> Map.new 
10

Вам не нужно звонить Enum.map отдельно, вы можете сделать это в Map.new:

[1,2,3,4] 
|> Enum.chunk(2) 
|> Map.new(fn [k, v] -> {k, v} end) 
3

Вы можете использовать хвостовую рекурсию для достижения этой цели:

defmodule Test do 
    def f(list, acc \\ []) 

    def f([x, y | xs], acc), do: f(xs, [{x, y} | acc]) 
    def f(_, acc), do: Enum.into(acc, %{}) 
end 

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

defmodule Benchmark do 

    # My solution 
    def alex(xs, acc \\ []) 
    def alex([x, y | xs], acc), do: alex(xs, [{x, y} | acc]) 
    def alex(_, acc), do: Map.new(acc) 

    # nietaki's solution 
    def nietaki(xs) do 
    xs 
    |> Enum.chunk(2) 
    |> Enum.map(fn [x, y] -> {x, y} end) 
    |> Map.new() 
    end 

    # Sheharyar's solution 
    def sheharyar(xs) do 
    xs 
    |> Enum.chunk(2) 
    |> Map.new(fn [x, y] -> {x, y} end) 
    end 

    # Your solution 
    def chip(xs) do 
    Enum.reduce(xs, %{}, fn(item, acc) -> 
     case Map.get(acc, :last) do 
     nil -> 
      Map.put(acc, :last, item) 
     last -> 
      acc = Map.put(acc, item, last) 
      Map.drop(acc, [:last]) 
     end 
    end) 
    end 

    # Patrick's solution 
    def patrick(xs) do 
    xs 
    |> Enum.chunk(2) 
    |> Enum.into(%{}, fn [x, y] -> {x, y} end) 
    end 

    # Function to do the time benchmarks. 
    def timed(f, list, times \\ 10) do 
    tests = 
     for _ <- 0..times do 
     :timer.tc(fn -> apply(__MODULE__, f, [list]) end) |> elem(0) 
     end 
    avg = Enum.sum(tests)/times 
    {f, avg} 
    end 

    # Test function. 
    def test(list, times \\ 10) do 
    list = Enum.to_list(list) 
    [:alex, :chip, :patrick, :nietaki, :sheharyar] 
    |> Enum.map(fn f -> timed(f, list, times) end) 
    |> Enum.sort(fn {_, x}, {_, y} -> x < y end) 
    end 
end 

Так bechmark для небольших списков является следующее:

iex(1)> Benchmark.test(0..4) 
[alex: 0.2, nietaki: 0.5, sheharyar: 0.6, chip: 0.8, patrick: 4.4] 

И для больших списков следующий:

iex(2)> Benchmark.test(0..1_000_000) 
[alex: 143105.7, nietaki: 241233.4, sheharyar: 254751.9, patrick: 501678.9, chip: 801616.5] 

Результаты - среднее время работы в микросекундах, а меньшее - лучше. Как вы можете видеть, хорошая релаксация хвоста (Benchmark.alex/1) делает лучшую работу в этом случае.

Я надеюсь, что это поможет :)

5

Использование Enum.into, который принимает преобразование функции в качестве второго параметра:

list 
|> Enum.chunk(2) 
|> Enum.into(%{}, fn [a, b] -> {a, b} end) 
0

Вы можете использовать enum.chunk

[1, 2, 3, 4] 
|> Enum.chunk(2) 
|> Enum.map(fn [a, b] -> {a, b} end) 
|> Map.new 

расколов перечисляемые на каждом элемент, для которого fun возвращает новое значение. Возвращает список списков.

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