在我的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
在我看来,这完全有道理。我觉得好像我错过了一个
elixir 正在做的底层事情......mapsum 然后运行匿名函数
在传入列表的头部(第一个值)。然后返回
结果被添加到 val,
好吧,函数的返回值被添加到 val 变量的 value 中,但这个总和永远不会分配给 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)]
接下来,elixir 替换 tail、func 和 val 的值,得到:
[1 | mapsum([2, 4], &(&1 * &1), 0)]
是一种无需在代码中显式写入= 即可为变量分配新值的方法。假设你有这个定义:
def repeat(_, 0), do: :ok
def repeat(greeting, times) do
IO.puts greeting
repeat(greeting, times-1)
end
然后你像这样调用函数:
repeat("hello", 4)
repeat() 执行时,elixir 需要在函数体中计算以下表达式:
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 是所谓的累加器:它将累加您有兴趣在递归结束时返回的结果。注意,在elixir中,函数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 参数变量。然后,您可以在 sum_list/2 定义的正文中为 acc 添加值:
def sum_list([head|tail], acc) do
new_acc = acc + head #<==== HERE
sum_list(tail, new_acc)
end
...并在递归函数调用中使用累加器的新值。注意最后的代码 sn-p 可以这样简化:
def sum_list([head|tail], acc) do
sum_list(tail, acc+head)
end
然后,当列表尾部为空列表时,递归结束,返回累加器:
def sum_list([], acc), do: acc
我在编写看似最简单的内容时遇到了很大的麻烦
长生不老药中的功能。
继续努力和尝试。当我开始使用 erlang 时,有时我会花一周时间尝试解决一个简单的递归问题。在您尝试进行此练习时,在我看来,您几乎发明了蓄能器概念,对您来说太好了。即使您没有找到解决方案,如果您挣扎了一会儿然后再查看解决方案,通常一个灯泡就会熄灭。使用递归会变得更容易。祝你好运!