如何将列表缩小为单个值

时间:2019-05-14 16:06:52

标签: elixir

我目前正在阅读《 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中最简单的函数时遇到了很多麻烦。回答上述问题,并详细说明我所缺少的内容,这将非常有用,谢谢!

4 个答案:

答案 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)] 函数第一次执行时,mapsumval=0返回func.(head),因此elixir将这些值替换为表达式,从而为您提供:

1

或:

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

接下来,长生不老药用[1 | mapsum(tail, func, val)] tailfunc的值代替,给您:

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))),则可能是累积值。

我们的第三个定义是主要逻辑的实际位置。它使用模式匹配将列表中的headtail分开。然后,它使用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

这有两件事:

  1. 列表为空时,打印acc累加值。
  2. 当列表不为空时,使用尾部递归,然后将头部添加到累加值中。