Scala 如何";转换;递归函数中的while循环

Scala 如何";转换;递归函数中的while循环,scala,recursion,while-loop,Scala,Recursion,While Loop,我从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

我从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循环:

while (dateTimeBegin.getTime().compareTo(dateTimeEnd.getTime()) == -1) {
  slotsString = dateTimeBegin.getTime().toString() :: slotsString
  dateTimeBegin.add(Calendar.MINUTE, 50)
}
它基本上选择dateTimeBegin和dateTimeEnd,并创建50分钟的插槽

这是while循环给出的输出:

2016年5月25日星期三12:00:00西部 2016年5月25日星期三12:50:00西部 2016年5月25日星期三13:40:00西部 2016年5月25日星期三14:30:00西部 2016年5月25日星期三15:20:00西部

然而,我知道这不是在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
    }
  }
但是函数不返回任何内容


谁能告诉我我做错了什么吗?

这里有一个基本的递归方法

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)

这里有一个基本的递归方法

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)

有几件事值得评论

首先,鉴于您只是想重新编写“while版本”,您对递归函数的想法并没有错。您只需进行以下小修复:

// var s = List[String]()
var s = l
顺便说一下,我不确定50分钟是否是打字错误,应该是60分钟,只是检查一下

一条建议:在“else”部分,您应该删除
return
关键字,因为在Scala中避免它是明智的。最后一个计算值是从函数返回的值,因此在函数结束时不需要它(就像现在的情况),强烈建议不要从函数中间返回

现在,谈谈更大的问题。你说“这不是用Scala编程的正确方法”。我同意,但您使用
循环这一事实不是问题<代码>虽然
是一种完全合法的循环方式。当然,
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)的情况下编写每一段代码。

有几点值得评论

首先,鉴于您只是想重新编写“while版本”,您对递归函数的想法并没有错。您只需进行以下小修复:

// var s = List[String]()
var s = l
顺便说一下,我不确定50分钟是否是打字错误,应该是60分钟,只是检查一下

一条建议:在“else”部分,您应该删除
return
关键字,因为在Scala中避免它是明智的。最后一个计算值是从函数返回的值,因此在函数结束时不需要它(就像现在的情况),强烈建议不要从函数中间返回

现在,谈谈更大的问题。你说“这不是用Scala编程的正确方法”。我同意,但您使用
循环这一事实不是问题<代码>虽然
是一种完全合法的循环方式。当然,
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)的情况下编写每一段代码。

提示:尝试在纸上构建调用堆栈。这有助于获得递归的感觉

您想要的是使用一个List[String]作为累加器,每次迭代都会添加另一个元素,当cB>=cE时,最终得到整个列表。 当cB 您甚至可以将列表作为当前状态提供给下一步,作为参数“l”。 但是,您可以使用它在“s”中创建一个新的列表实例。每一步都要重新开始。
最后,条件为false,返回s,但s是新创建的空列表。

提示:尝试在纸上构建调用堆栈。这有助于获得递归的感觉

您想要的是使用一个List[String]作为累加器,每次迭代都会添加另一个元素,当cB>=cE时,最终得到整个列表。 当cB 您甚至可以将列表作为当前状态提供给下一步,作为参数“l”。 但是,您可以使用它在“s”中创建一个新的列表实例。每一步都要重新开始。
最后,条件为false,返回s,但s是新创建的空列表。

有一种几乎完全机械的方法可以将任何while循环转换为尾部递归函数。让我们考虑只使用while循环来设置某些状态的情况。(你没有打印或其他任何东西,这会导致副作用消失在某处。)

从根本上说,虽然没有远距离副作用的循环看起来是这样的:

while (test(state)) {
  state = f(state);
}
这就变成了

def loop(state: State): State =
  if (test(state)) loop(f(state))
  else state
让我们