В моем экземпляре 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, я иногда проводил неделю, пытаясь решить простую рекурсивную проблему. В ваших попытках с этим упражнением мне кажется, что вы почти изобрели концепцию аккумулятора, так хорошо для вас. Даже если вы не нашли решения, если вы некоторое время боретесь, а затем смотрите на решение, обычно лампочка гаснет. Использование рекурсии станет проще. Удачи!