我目前正在阅读《 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]
的答案了。
之后,我尝试添加将值加在一起的代码。
在尝试这样做时,我的Elixir控制台抛出一个错误,说我将一个空列表作为函数[]的第一个参数传递。这是我现在正在做的,以获得[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。
这应该起作用,直到传递了一个空列表并被第一个函数捕获为止。
我不确定我在这里缺少什么。我在编写似乎是Elixir中最简单的函数时遇到了很多麻烦。回答上述问题,并详细说明我所缺少的内容,这将非常有用,谢谢!
答案 0 :(得分:3)
在我的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然后运行anon函数 在传入列表的开头(第一个值)上。然后那又回来了 结果已添加到val,
好吧,该函数的返回值被添加到val
变量的 value 中,但是该总和从未分配给{{1 }}变量。这是iex中的一个示例:
val
要将某些内容添加到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)
,因此elixir将这些值替换为表达式,从而为您提供:
1
或:
[1 + 0 | mapsum(tail, func, val)]
接下来,长生不老药用[1 | mapsum(tail, func, val)]
,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)
时,elixir将需要在函数的主体中评估以下表达式:
repeat()
repeat(greeting, times-1)
和repeat()
第一次执行,因此elixir首先将这些变量的值替换为表达式,如下所示:
greeting="hello"
为您提供:
times=4
接下来,该函数调用中的参数将与函数参数变量匹配,如下所示:
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
解决方案相反,我通常发现使用称为 accumulator 的递归解决方案更容易,这是一个基本的递归技巧。该书尚未在示例中使用累加器,但这是一个使用累加器的简单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
我在写似乎最简单的书时遇到了很多麻烦 药剂中的功能。
继续努力奋斗。当我开始使用erlang时,有时会花一个星期来尝试解决一个简单的递归问题。在您尝试进行此练习时,在我看来,您几乎发明了蓄能器概念,对您如此好。即使您找不到解决方案,但如果您挣扎了一段时间,然后再查看解决方案,通常灯泡都会熄灭。使用递归会更容易。祝你好运!
答案 1 :(得分:1)
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
部分。
答案 2 :(得分:0)
我想面临的挑战是不使用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
sum和map都可以使用reduce来实现,这可能是后续练习:)
答案 3 :(得分:0)
这是在做什么:
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
这有两件事:
acc
累加值。