我从Scala开始,并且很难在功能上思考。
我有以下变量:
val dateTimeBegin = Calendar.getInstance()
dateTimeBegin.set(2016, Calendar.MAY, 25, 12, 0, 0)
val dateTimeEnd = Calendar.getInstance()
dateTimeEnd.set(2016, Calendar.MAY, 25, 16, 0, 0)
var slotsString = List[String]()
var slots = List[Calendar]()
然后,我在循环中写了这个:
while (dateTimeBegin.getTime().compareTo(dateTimeEnd.getTime()) == -1) {
slotsString = dateTimeBegin.getTime().toString() :: slotsString
dateTimeBegin.add(Calendar.MINUTE, 50)
}
它基本上选择dateTimeBegin和dateTimeEnd并创建50分钟的插槽。
这是while循环给出的输出:
Wed May 25 12:00 12:00 5月25日星期三12:50:00 2016年西部 5月25日星期三13:40:00 2016年西部 5月25日星期三14:30:00 2016年西部 5月25日星期三15:20:00 WEST 2016
但是,我知道这不是在Scala中编程的正确方法,我应该使用递归函数。 我试着写这个函数:
def fillSlots(l: List[String], cB: Calendar, cE: Calendar): List[String] = {
var s = List[String]()
if (cB.getTime().compareTo(cE.getTime()) == -1) {
s = cB.getTime().toString() :: s
cB.add(Calendar.MINUTE, 50)
fillSlots(s, cB, cE)
} else {
return s
}
}
但该功能不会返回任何内容。
谁能告诉我我做错了什么?
答案 0 :(得分:3)
有一种几乎完全机械的方式将任何while循环转换为尾递归函数。让我们考虑使用while循环 来设置某种状态的情况。 (你不打印或其他什么,这会导致副作用在某处消失。)
从根本上说,没有远处副作用的循环看起来像这样:
while (test(state)) {
state = f(state);
}
那就变成了
def loop(state: State): State =
if (test(state)) loop(f(state))
else state
让我们思考这是做什么的。我们进入循环并测试状态。如果通过,我们会将state
更改为f(state)
并再次转到。如果它没有通过,我们将以我们所拥有的任何状态返回。
如果你看一下while循环 - 这也正是它的作用,假设你在离开循环后恢复状态。
现在,将所有状态都放在一个对象中可能会很尴尬,但是没有理由不能拥有多个参数。那么,你的国家是什么?它是slotStrings
和dateTimeBegin
。 slotStrings
开始为空,您可以使用初始参数进行模仿。而且我们并不关心所有状态,只关注slotStrings
,因此我们只会(最终)返回该状态,即使我们传递了参数中的所有状态。因此,在删除所有不必要的空()
之后(Scala可以推断出那些),我们遵循机械过程并得到:
def loop(dTB: Calendar, sS: List[String] = Nil): List[String] =
if (dTB.getTime.compareTo(dateTimeEnd.getTime) == -1) {
val sS2 = dTB.getTime.toString :: sS
dtB.add(Calendar.MINUTE, 50)
loop(dtB, sS2)
}
else sS
现在,习惯上不修改适当的内容(但所有Calendar
都可以 - 你可以使用java.time.LocalDateTime
代替它返回一个新实例及其{ {1}}方法),您可以向函数添加更多参数(例如plus
),这些参数实际上不会改变状态但可以随身携带。
但这是基本的转换:不是让循环再次运行,所有状态隐式从迭代传递到迭代,而是在递归调用中明确命名它。
(注意:如果你在递归之前没有组装所有状态,它就不会是尾递归的!没有dateTimeEnd
!你必须传递<当然,你仍然可以写一个递归函数,但如果它需要非常递归,它会溢出堆栈。)
答案 1 :(得分:2)
有几件值得评论的事情。
首先,你的递归函数的想法并没有错,因为你只想重写“while version”。你只需要做一个小修复:
// var s = List[String]()
var s = l
顺便说一句,不确定50分钟是否是一个错字,应该是60,只是检查。
一条建议:在“else”部分中,您应该删除return
关键字,因为在Scala中避免它是明智的。最后计算的值是从函数返回的值,因此您不需要在函数末尾(就像现在的情况一样),并且强烈建议不要从函数的中间返回。
现在,处理更大的问题。你说“这不是在Scala中编程的正确方法”。我同意,但你使用while
循环的事实不是问题; while
是一种完全合法的循环方式。当然,map
是首选,但while
不是“禁止”(例如,有时你可能会有非常长的列表和递归函数会导致堆栈因此while
是完全有效的选择) 。
真正的问题是你正在使用var
s(可变变量)。以下是如何在没有它们的情况下重新编写函数:
def fillSlots(cB: Calendar, cE: Calendar): List[String] = {
if (cB.getTime().compareTo(cE.getTime()) == -1) {
cB.add(Calendar.MINUTE, 50)
cB.getTime().toString() :: fillSlots(cB, cE)
} else Nil
}
由于递归的性质,此列表将从之前的时间点开始(例如13:00,13:50,14:40等)。
不要气馁;事实上,你正在努力以“正确的方式”编写Scala代码是最重要的事情。您将随时了解所需的所有内容。我现在可以给你的最重要的建议是尝试编写每段代码而不使用可变变量(vars)。
答案 2 :(得分:1)
这是一种基本的递归方法。
import java.util.Calendar
def fillSlots(cB: Calendar, cE: Calendar)(
acc: List[String] = List(cB.getTime().toString())): List[String] =
if ((cB.getTime() compareTo cE.getTime()) >= 0)
acc.reverse
else {
cB.add(Calendar.MINUTE, 50)
fillSlots(cB, cE)(cB.getTime().toString() :: acc)
}
用法:
scala> fillSlots(dateTimeBegin, dateTimeEnd)()
res148: List[String] = List(Wed May 25 12:00:00 PDT 2016, Wed May 25 12:50:00 PDT 2016, Wed May 25 13:40:00 PDT 2016, Wed May 25 14:30:00 PDT 2016, Wed May 25 15:20:00 PDT 2016, Wed May 25 16:10:00 PDT 2016)
如果你只需要一定数量的增量,你可以做这样的事情。
scala> (1 to 6).map{_ => dateTimeBegin.add(Calendar.MINUTE, 50)
| dateTimeBegin.getTime().toString() }
res151: scala.collection.immutable.IndexedSeq[String] = Vector(Wed May 25 12:50:00 PDT 2016, Wed May 25 13:40:00 PDT 2016, Wed May 25 14:30:00 PDT 2016, Wed May 25 15:20:00 PDT 2016, Wed May 25 16:10:00 PDT 2016, Wed May 25 17:00:00 PDT 2016)
答案 3 :(得分:0)
提示:尝试在纸上构建一个调用堆栈。这有助于感受递归。
你想要的是使用List [String]作为累加器,其中每次迭代添加另一个元素,最后得到整个列表,当cB&gt; = cE时。 当cB < cE你调用函数的下一个递归。
您甚至将列表作为当前状态提供给下一步,作为参数“l”。 但是使用它,你在's'中创建一个新的列表实例。每一步都重新开始。 最后条件为false并返回s,但s是新创建的空列表。