Как уменьшить список до одного значения - PullRequest
3 голосов
/ 14 мая 2019

В настоящее время я читаю книгу Programming Elixir 1.6, и на странице 88 есть задача создать функцию (mapsum), которая принимает список, и функцию.

Эта функция mapsum должна принимать функцию, предоставленную в качестве параметра, выполнять итерацию по каждому элементу в списке из-за рекурсии, после обработки каждого числа в списке все результаты должны быть добавлены.

Например:

[1, 2, 3], &(&1 * &1) - параметры переданы в mapsum.

Ожидаемый результат:

[1, 4, 9] # Answer after each number in map has been effected by function.
[14] # Final answer after the values are added.

Прямо сейчасЯ могу получить ответ [1, 4, 9] для печати. ​​
После этого я попытался добавить код, который бы суммировал значения вместе.

При попытке сделать это, моя консоль эликсира выдает ошибку, говоря, что я передаю пустой список в качестве первого аргумента функции [].Вот что я делаю сейчас, чтобы получить ответ [1, 4, 9]:

def mapsum([], _fun), do: []

def mapsum([head | tail], func) do
    [func.(head) | mapsum(tail, func)]
end

Таким образом, как вы можете видеть, он принимает список и функцию так же, как было сказано в задании.Чтобы попытаться получить сложение значений, вот что я пытался НЕ РАБОТАТЬ:

def mapsum([], _fun, val), do: []

def mapsum([head | tail], func, val \\ 0) do
    [func.(head) + val | mapsum(tail, func, val)]
end

На мой взгляд, это имеет смысл.Я чувствую, что мне не хватает того, что делает эликсир.

Вот что я ожидаю, что произойдет с тем, что я набрал: mapsum получает список [1, 2, 3] и функцию &(&1 * &1), val может быть задан как 0 или оставлен как одинимеет значение по умолчанию 0. Затем Mapsum запускает функцию anon на заголовке (первое значение) переданного списка.Затем этот возвращаемый результат добавляется в val, после чего мы затем вызываем mapsum на хвосте (или оставшиеся значения) с той же функцией и обновляемым val.

Это должно работать, пока не будет передан пустой списоки пойман первой функцией.

Я не уверен, что мне здесь не хватает.У меня были большие проблемы с написанием того, что кажется самым простым в эликсире.Ответ на вышеуказанную проблему с подробным объяснением того, что мне не хватает, был бы невероятно полезен, спасибо!

Ответы [ 4 ]

3 голосов
/ 14 мая 2019

В моем экземпляре Programming Elixir 1.6 упражнение включено p. 77 (не стр. 88).

Expected Outcome:

`[1, 4, 9]` - Answer after each number in map has been effected by function.

`[14]` - Final answer after the values are added.

После описания упражнения моя книга приводит следующий пример:

iex> MyList.mapsum [1, 2, 3], &(&1 * &1)
14

Ожидаемый результат - 14, и нет промежуточного результата, как ожидалось [1, 4, 9].

def mapsum([], _fun, val), do: []
def mapsum([head | tail], func, val \\ 0) do
    [func.(head) + val | mapsum(tail, func, val)]
end

На мой взгляд, это имеет смысл.Я чувствую, как будто я упускаю основную вещь, которую делает эликсир ... mapsum затем запускает анон-функцию на голове (первое значение) переданного в списке.Затем этот возвращаемый результат добавляется к значению val,

. Возвращаемое значение функции добавляется к значению изпеременная val, но эта сумма никогда не присваивается переменной val.Вот пример в iex:

iex(5)> val = 0
0

iex(6)> val + 1
1

iex(7)> val
0

Чтобы добавить что-то в переменную val, вы должны написать:

iex(8)> val = val + 1
1

Вместо этого вы написали следующее выражение:

[func.(head) + val | mapsum(tail, func, val)]

При первом выполнении функции mapsum val=0 и func.(head) возвращает 1, поэтому elixir подставляет эти значения в ваше выражение, давая вам:

[1 + 0 | mapsum(tail, func, val)]

или:

[1 | mapsum(tail, func, val)]

Далее эликсир подставляет в значения для tail, func и val, давая вам:

[1 | mapsum([2, 4], &(&1 * &1), 0)]  

Там способ присвоить новое значение переменной без явного написания = в вашем коде.Предположим, у вас есть этот def:

def repeat(_, 0), do: :ok

def repeat(greeting, times) do
  IO.puts greeting
  repeat(greeting, times-1)
end

, и вы вызываете функцию следующим образом:

repeat("hello", 4)

Когда выполняется repeat(), эликсиру нужно будет вычислить следующее выражение в телефункция:

repeat(greeting, times-1)

При первом выполнении repeat(), greeting="hello" и times=4, поэтому elixir сначала подставляет значения этих переменных в выражение, например:

repeat("hello", 4-1)  

, что дает вам:

repeat("hello", 3)

Далее, аргументы в этом вызове функции сопоставляются с переменными параметра функции следующим образом:

        repeat("hello",    3 )     #<===function call
                  |        |
 greeting="hello" |        | times=3
                  V        V
    def repeat(greeting, times) do  #<====function definition

Другими словами, вызов функцииприводит к неявному присвоению аргументов переменным параметров функции.Затем внутри тела функции вы можете использовать имена greeting и times, чтобы получить их соответствующие значения.

Но в вашем выражении:

[func.(head) + val | mapsum(tail, func, val)]

часть:

func.(head) + val

не является аргументом вызова функции, поэтому сумма не присваиваетсялюбая переменная параметра.

Спойлер: мое решение следует
_
_
_
_
_
_
_
_
_
_
_
_
_
_
_
_

  def mapsum([head|tail], func) do
      func.(head) + mapsum(tail, func) 
  end

  def mapsum([], _func), do: 0

Это решение использует ту же логикув качестве примера на p. 73, который находит длину списка:

def len([head|tail]) do
  1 + len(tail)
end

... с завершающим регистром:

def len([]), do: 0

Вопреки моему решению mapsum()выше, как правило, мне проще найти рекурсивное решение, используя так называемый аккумулятор , который является основным приемом рекурсии.Книга еще не использовала аккумулятор в примере, но вот простой пример sum_list(), в котором используется аккумулятор:

  def sum_list(list), do: sum_list(list, 0)

  def sum_list([], acc), do: acc
  def sum_list([head|tail], acc) do
    sum_list(tail, acc+head)
  end

Вы преобразуете вызов функции sum_list(list) в вызов функции с двумя аргументами, что делается в этой строке:

def sum_list(list), do: sum_list(list, 0)

Второй аргумент, 0, является так называемым аккумулятором: он будет накапливать результат, который вы заинтересованы в возвращении в конце рекурсии.Обратите внимание, что в эликсире функции sum_list/1 и sum_list/2 являются совершенно разными функциями и не имеют ничего общего друг с другом.Если это облегчает понимание, вы можете использовать другое имя для функции с двумя аргументами, например:

def sum_list(list), do: my_helper(list, 0)

Если list не пусто, вызов функции с двумя аргументами будет соответствовать этому предложению функции:

  def sum_list([head|tail], acc) do

и 0 будут присвоены переменной параметра acc.Затем вы можете добавить значения к acc в теле определения sum_list/2:

def sum_list([head|tail], acc) do
  new_acc = acc + head  #<==== HERE
  sum_list(tail, new_acc)  
end

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

def sum_list([head|tail], acc) do
  sum_list(tail, acc+head)  
end

Затем, когда хвост списка является пустым списком, завершающим рекурсию, вы возвращаете аккумулятор:

def sum_list([], acc), do: acc

У меня были большие проблемы с написанием того, что кажется самым простым функций в эликсире.

Продолжай бороться и пытаться. Когда я начал работать с Erlang, я иногда проводил неделю, пытаясь решить простую рекурсивную проблему. В ваших попытках с этим упражнением мне кажется, что вы почти изобрели концепцию аккумулятора, так хорошо для вас. Даже если вы не нашли решения, если вы некоторое время боретесь, а затем смотрите на решение, обычно лампочка гаснет. Использование рекурсии станет проще. Удачи!

1 голос
/ 14 мая 2019
def mapsum(list, fun, val \\ 0)
def mapsum([], _fun, val), do: val
def mapsum([head | tail], fun, val), do: mapsum(tail, fun, val + fun.(head))

Давайте пройдемся по этой строке за строкой.

Наше первое определение просто утверждает, что третий аргумент имеет значение по умолчанию 0.Ничего более.

Наше второе определение говорит, что если у нас будет пустой список, он вернет текущий val.Это может быть 0, если мы передали в пустом списке (mapsum([], &(&1 * &1))), или это может быть накопленное значение при передаче в непустом списке (mapsum([1,2,3], &(&1 * &1))).

Наше третье определениегде основная логика на самом деле.Он использует сопоставление с образцом для разделения head и tail списка.Затем он вызывает нашу функцию рекурсивно, используя tail в качестве нового первого аргумента, сохраняя fun как есть, и изменяя val так, что он принимает текущий val и добавляет результат fun, примененный кhead.

Давайте посмотрим, как это на самом деле расширяется.

mapsum([1,2,3], &(&1 * &1))

mapsum([2,3], &(&1 * &1), 0 + fun.(1)
mapsum([2,3], &(&1 * &1), 1)

mapsum([3], &(&1 * &1), 1 + fun.(2)
mapsum([3], &(&1 * &1), 5)

mapsum([], &(&1 * &1), 5 + fun.(3)
mapsum([], &(&1 * &1), 14)

14

Это отличается от вашего подхода, потому что здесь я подвожу итог, где ваше решение выполнило часть map (этосоздал новый список с fun, примененным к каждому элементу), но вы не смогли применить фактическую часть sum к новому списку.

0 голосов
/ 17 мая 2019

Что это делает:

def mapsum([head | tail], func, val \\ 0) do
    [func.(head) + val | mapsum(tail, func, val)]
end

запускает func для каждого элемента и добавляет к нему 0 (или val) - но val остается неизменным и никогда не обновляется, а функция по-прежнему возвращает список.

Я предлагаю вам переименовать val в acc, потому что он должен содержать накопленное значение. Тогда вы можете написать это так:

def mapsum([], _fun, acc), do: acc

def mapsum([head | tail], func, acc \\ 0) do
  mapsum(tail, func, acc + func.(head))
end

Это делает две вещи:

  1. Когда список пуст, выведите значение acc umulated.
  2. Если список не пустой, выполнить рекурс с хвостом и добавить голову к накопленному значению.
0 голосов
/ 16 мая 2019

Полагаю, задача состоит в том, чтобы реализовать это, не используя модуль Enum. Я бы все еще начал с реализации этого с использованием модуля Enum, а затем переопределил функции, используемые в Enum.

defmodule MyList 
  do def mapsum(list, f) do 
    Enum.map(list, f) |> Enum.sum 
  end 
end

Следующим шагом будет переопределение карты и сумма

defmodule MyList do
  def mapsum(list, f) do
    map(list, f) |> sum
  end

  defp sum([head | tail]) do
    head + sum(tail)
  end
  defp sum([]) do
    0
  end

  defp map([head | tail], f) do
    [f.(head)|map(tail, f)]
  end
  defp map([], _f) do
    []
  end

end

Как сумма, так и карта могут быть реализованы с использованием функции снижения, что может быть последующим упражнением :)

...