我有以下Scala片段。为了解决我给出的问题,我“欺骗”了一点并使用var
- 本质上是一种非最终的,可变的数据类型。它的值在循环的每次迭代中更新。我花了很多时间试图找出如何使用递归和不可变数据类型和列表来做到这一点。
原始摘录:
def countChange_sort(money: Int, coins: List[Int]): Int =
if (coins.isEmpty || money < 0)
0
else if (coins.tail.isEmpty && money % coins.head != 0) {
0
} else if (coins.tail.isEmpty && money % coins.head == 0 || money == 0) {
1
} else {
-- redacted --
}
}
基本上,我是否可以使用任何基本技术来消除i
,尤其是累积的cnt
变量?
谢谢!
答案 0 :(得分:3)
有许多不同的方法来解决功能风格的问题。通常,您首先以与设计命令式算法时不同的方式分析问题,然后编写命令式算法,然后转换&#34;它是功能性的,不能产生非常自然的功能算法(你经常会错过功能风格的许多潜在好处)。但是,如果你是一位经验丰富的势在必行的程序员,只需要开始使用函数式编程,这就是你所拥有的,而且这是开始理解新概念的好方法。所以这里是你如何处理&#34;转换&#34;这样的功能就像你以一种相当无创造的方式写入功能风格的功能(即没有提出不同的算法)。
让我们考虑else
表达式,因为其余的都没问题。
功能样式没有循环,所以如果你需要多次运行代码块(命令循环的主体),那么代码块必须是一个函数。通常函数是一个简单的非递归函数,你可以调用高阶函数(如map或fold)来进行实际的递归,但我会假设你需要递归练习思考并希望看到它明确。循环条件是根据循环体中你手头的数量来计算的,所以我们只是根据完全相同的条件递归地调用循环替换函数:
} else {
var cnt = 0
var i = 0
def loop(????) : ??? = {
if (money - (i * coins.head) > 0) {
cnt += countChange_sort(money - (i * coins.head), coins.tail)
i = i + 1
loop(????)
}
}
loop(????)
cnt
}
信息仅通过其输入参数或通过其定义传递给函数,并通过其返回值从函数传递。
在创建函数时(在编译时或在创建闭包时的运行时),通过其定义进入函数的信息是常量。对cnt
和i
中包含的信息听起来非常有用,每次调用都需要有所不同。所以他们显然需要作为参数传递:
} else {
var cnt = 0
var i = 0
def loop(cnt : Int, i : Int) : ??? = {
if (money - (i * coins.head) > 0) {
cnt += countChange_sort(money - (i * coins.head), coins.tail)
i = i + 1
loop(cnt, i)
}
}
loop(cnt, i)
cnt
}
但我们希望在函数调用后使用cnt
的最终值。如果仅通过>> loop
通过其返回值传递信息,那么我们只能通过让cnt
返回loop
来获取} else {
var cnt = 0
var i = 0
def loop(cnt : Int, i : Int) : Int = {
if (money - (i * coins.head) > 0) {
cnt += countChange_sort(money - (i * coins.head), coins.tail)
i = i + 1
loop(cnt, i)
} else {
cnt
}
}
cnt = loop(cnt, i)
cnt
}
的最后一个值。这很简单:
coins
money
,countChange_sort
和coins
是信息的示例&#34;通过其定义输入功能&#34;。 money
和loop
甚至是&#34;变量&#34;,但它们在定义loop
时保持不变。如果您想将countChange_sort
移出coins
的正文以成为一个独立的函数,则必须使money
和countChange_sort
有其他参数;它们将从loop
中的顶级调用传入,然后在loop
内的每个递归调用中未经修改地传递。这仍然会使countChange_sort
依赖于*
本身(以及算术运算符-
和loop
!),所以你永远不会真正摆脱函数知道关于通过其论点不会进入的外部事物。
看起来很不错。但我们仍在cnt
内部使用赋值语句,这是不对的。不过我们所做的就是为i
和loop
分配新值,然后将它们传递给} else {
var cnt = 0
var i = 0
def loop(cnt : Int, i : Int) : Int = {
if (money - (i * coins.head) > 0) {
loop(cnt + countChange_sort(money - (i * coins.head), coins.tail), i + 1)
} else {
cnt
}
}
cnt = loop(cnt, i)
cnt
}
的递归调用,这样就可以轻松删除这些分配:
cnt
现在有一些明显的简化,因为除了初始化它们之外我们根本没有做任何与可变i
和cnt
的任何事情,然后传递它们的初始值,分配给cnt
一次,然后立即返回。所以我们可以(最终)完全摆脱可变i
和} else {
def loop(cnt : Int, i : Int) : Int = {
if (money - (i * coins.head) > 0) {
loop(cnt + countChange_sort(money - (i * coins.head), coins.tail), i + 1)
} else {
cnt
}
}
loop(0, 0)
}
:
i
我们已经完成了!看不到任何副作用!
请注意,我对你的算法实际上做了什么并没有多想(我甚至没有试图弄清楚它是否真的正确,尽管我认为它是正确的)。我所做的一切都直截了当地应用了一般原则,即信息只通过其参数进入函数并通过其返回值离开;表达式可访问的所有可变状态实际上是表达式的额外隐藏输入和隐藏输出。使它们成为不可变的显式输入和输出,然后允许您删除不需要的输入和输出。例如,loop
不需要包含在loop
的返回值中,因为它实际上并不需要它,所以转换为功能样式已经明确了它纯粹是cnt
内部的,而你必须真正阅读命令式版本的代码来推断它。
i
和cnt
就是所谓的 accumulators 。累积器并不特别,它们只是普通的论点;该术语仅指它们的使用方式。基本上,如果你的算法需要跟踪一些数据,你可以引入一个累加器参数,这样每个递归调用都可以“向前传递”#34;到目前为止所做的数据。它们通常填补了本地临时可变变量填充命令式算法的作用。
一旦确定递归函数的返回值为累加器参数的值,这是一个非常常见的模式,一旦确定不再需要做任何工作,就像{{ 1}}在这种情况下。
请注意,这些技术并不一定会产生良好的功能代码,但转换使用&#34; local&#34;实现的功能非常容易。使用这种技术将可变状态转换为功能样式。普遍的非本地使用可变性,例如大多数传统OO程序的典型使用,更难以像这样转换;你可以这样做,但你往往必须立即修改整个程序,结果函数有大量的额外参数(明确暴露原始程序中存在的所有隐藏数据流)。
答案 1 :(得分:1)
我没有任何基本技术来更改您具体的代码。但是,这是解决递归算法的一般提示:
你能把问题分解成子问题吗?例如,在钱的例子中,如果你试图以5美元的价格获得10美元,这与5美元(已经选择5美元一次)获得5美元的问题类似。尝试绘制出来并制定规则。你会惊讶地发现你的解决方案更加正确。
答案 2 :(得分:1)
由于没有人回答你的问题,我会尝试给你一些提示: 什么是循环? 遍历集合的每个元素。停止满足条件
你可以用递归做什么: 遍历集合的每个元素。停止满足条件。
开始简单编写一个没有vars的方法,它打印一个集合的每个元素。 然后其余部分变得简单,看看你的循环以及你在做什么。 而不是直接操作变量(比如i = i + 1),只需将i + 1传递给方法的递归调用。
HTH