【问题标题】:Scala extending while loops to do-until expressionsScala 将 while 循环扩展为 do-until 表达式
【发布时间】:2012-11-25 16:46:21
【问题描述】:

我正在尝试用 Scala 做一些实验。我想重复这个实验(随机),直到出现预期的结果并得到那个结果。如果我使用 while 或 do-while 循环执行此操作,那么我需要编写(假设“body”代表实验,“cond”表示是否符合预期):

do {
  val result = body
} while(!cond(result))

但是,它不起作用,因为最后一个条件不能引用循环体中的局部变量。我们需要像这样稍微修改一下这个控制抽象:

def repeat[A](body: => A)(cond: A => Boolean): A = {
  val result = body
  if (cond(result)) result else repeat(body)(cond)
}

它以某种方式工作,但对我来说并不完美,因为我需要通过传递两个参数来调用此方法,例如:

val result = repeat(body)(a => ...)

我想知道是否有更有效、更自然的方式来做到这一点,让它看起来更像一个内置结构:

val result = do { body } until (a => ...)

没有返回值的 body 的一个很好的解决方案可以在这篇文章中找到:How Does One Make Scala Control Abstraction in Repeat Until?,最后一个单行答案。该答案中的body 部分不返回值,因此until 可以是新AnyRef 对象的方法,但该技巧不适用于此处,因为我们希望返回A 而不是@ 987654330@。有什么办法可以做到这一点?谢谢。

【问题讨论】:

  • 为什么你不喜欢这段代码中的递归?它是尾递归,所以堆栈不会增长。
  • 我对尾递归优化不太有信心。我曾经使用该技术编写了一个 BFS 算法,它仍然会堆栈溢出。我可能会就此发表另一个问题。无论如何,这只是一个小问题,我确实想看看如何让我的代码看起来更原生。
  • 如果你得到一个stackoverflow,它不是尾递归的。您可以通过添加@annotation.tailrec 告诉编译器一个方法应该是尾递归的。如果不是,编译器将失败。
  • 谢谢,我刚刚解决了这个问题。我在没有 final 关键字的抽象类中定义了尾递归函数。编译器没有做任何优化,所以会溢出。

标签: scala


【解决方案1】:

你正在混合编程风格并因此而陷入困境。

除非您在其中执行某种副作用,否则您的循环仅适用于加热处理器。

do {
  val result = bodyThatPrintsOrSomething
} until (!cond(result))

因此,如果您要使用副作用代码,只需将条件放入 var:

var result: Whatever = _
do {
  result = bodyThatPrintsOrSomething
} until (!cond(result))

或等价物:

var result = bodyThatPrintsOrSomething
while (!cond(result)) result = bodyThatPrintsOrSomething

或者,如果您采用函数式方法,则无论如何您都必须返回计算结果。然后使用类似的东西:

Iterator.continually{ bodyThatGivesAResult }.takeWhile(cond)

(众所周知,Iterator 的烦恼是没有很好地获取列表中所有好的以及第一个坏的)。

或者你可以使用你的repeat 方法,它是尾递归的。如果您不相信它,请检查字节码(使用javap -c),添加@annotation.tailrec 注释以便编译器在它不是尾递归时抛出错误,或者使用var方法:

def repeat[A](body: => A)(cond: A => Boolean): A = {
  var a = body
  while (cond(a)) { a = body }
  a
}

【讨论】:

  • 谢谢。我知道循环是必要的,所以想出一些更实用的东西,比如重复函数。它对我有用,但我希望它更像是在 stackoverflow.com/questions/3036058/… 中的那个
  • +1 用于提及迭代器方法并警告整个构造依赖于体内的副作用。
【解决方案2】:

只需稍加修改,您就可以将当前的方法转换为一种迷你流畅的 API,从而产生接近您想要的语法:

class run[A](body: => A) {
  def until(cond: A => Boolean): A = {
    val result = body
    if (cond(result)) result else until(cond)
  }
}
object run {
  def apply[A](body: => A) = new run(body)
}

由于do 是保留字,我们必须使用run。结果现在看起来像这样:

run {
  // body with a result type A
} until (a => ...)

编辑:

我刚刚意识到我几乎重新发明了链接 question 中已经提出的内容。扩展该方法以返回类型 A 而不是 Unit 的一种可能性是:

def repeat[A](body: => A) = new {
  def until(condition: A => Boolean): A = {
    var a = body
    while (!condition(a)) { a = body }
    a     
  }   
}

【讨论】:

  • @Yang:你没有评论吗?这是我的回答:为什么要从repeat 本身返回A?由于您调用until,因此只有此返回类型是相关的。我刚刚测试了类似:val result = repeat { Random.nextInt(10) } until (_ == 0).
  • 我明白了。这就是我要找的。谢谢!
  • 是的,我添加了那个注释,但我很快就意识到了 until 函数的真正含义。在语法repeat {...} until () 中,until 只是一个函数,因此返回应该来自until 而不是repeat。再次感谢,这对我来说很有趣。
  • 不客气!我认为在我的回答中保留第一个更详细的版本也更好,因为在这个版本中(可能)更清楚until 只是一个返回A 的成员函数。
【解决方案3】:

为了记录之前提出的建议的派生,我采用了 repeat { ... } until(...) 的尾递归实现,其中还包括对迭代次数的限制:

def repeat[A](body: => A) = new {
  def until(condition: A => Boolean, attempts: Int = 10): Option[A] = {
    if (attempts <= 0) None
    else {
      val a = body
      if (condition(a)) Some(a)
      else until(condition, attempts - 1)
    }
  }
}

这允许循环在 attempts 执行主体后退出:

scala> import java.util.Random
import java.util.Random

scala> val r = new Random()
r: java.util.Random = java.util.Random@cb51256

scala> repeat { r.nextInt(100) } until(_ > 90, 4)
res0: Option[Int] = Some(98)

scala> repeat { r.nextInt(100) } until(_ > 90, 4)
res1: Option[Int] = Some(98)

scala> repeat { r.nextInt(100) } until(_ > 90, 4)
res2: Option[Int] = None

scala> repeat { r.nextInt(100) } until(_ > 90, 4)
res3: Option[Int] = None

scala> repeat { r.nextInt(100) } until(_ > 90, 4)
res4: Option[Int] = Some(94)

【讨论】:

    猜你喜欢
    • 2015-02-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-08-28
    • 1970-01-01
    • 1970-01-01
    • 2018-11-21
    相关资源
    最近更新 更多