如何摆脱Scala中的循环?

Tia*_*HUo 267 for-loop scala tail-recursion break

我如何打破循环?

var largest=0
for(i<-999 to 1 by -1) {
    for (j<-i to 1 by -1) {
        val product=i*j
        if (largest>product)
            // I want to break out here
        else
           if(product.toString.equals(product.toString.reverse))
              largest=largest max product
    }
}
Run Code Online (Sandbox Code Playgroud)

如何将嵌套for循环转换为尾递归?

来自FOSDEM 2009 上的Scala Talk http://www.slideshare.net/Odersky/fosdem-2009-1013261在第22页:

打破并继续Scala没有它们.为什么?他们有点必要; 更好地使用许多较小的函数问题如何与闭包交互.他们不需要!

解释是什么?

Rex*_*err 363

你有三个(左右)选项来摆脱循环.

假设你要总和数字,直到总数大于1000.你试试

var sum = 0
for (i <- 0 to 1000) sum += i
Run Code Online (Sandbox Code Playgroud)

除了你想要停止时(总和> 1000).

该怎么办?有几种选择.

(1a)使用包含您测试的条件的一些构造.

var sum = 0
(0 to 1000).iterator.takeWhile(_ => sum < 1000).foreach(i => sum+=i)
Run Code Online (Sandbox Code Playgroud)

(警告 - 这取决于在评估期间takeWhile测试和foreach是如何交错的细节,并且可能不应该在实践中使用!).

(1b)使用尾递归而不是for循环,利用在Scala中编写新方法的容易程度:

var sum = 0
def addTo(i: Int, max: Int) {
  sum += i; if (sum < max) addTo(i+1,max)
}
addTo(0,1000)
Run Code Online (Sandbox Code Playgroud)

(1c)回到使用while循环

var sum = 0
var i = 0
while (i <= 1000 && sum <= 1000) { sum += 1; i += 1 }
Run Code Online (Sandbox Code Playgroud)

(2)抛出异常.

object AllDone extends Exception { }
var sum = 0
try {
  for (i <- 0 to 1000) { sum += i; if (sum>=1000) throw AllDone }
} catch {
  case AllDone =>
}
Run Code Online (Sandbox Code Playgroud)

(2a)在Scala 2.8+中,这已经预先打包scala.util.control.Breaks使用的语法看起来很像你熟悉的C/Java旧版:

import scala.util.control.Breaks._
var sum = 0
breakable { for (i <- 0 to 1000) {
  sum += i
  if (sum >= 1000) break
} }
Run Code Online (Sandbox Code Playgroud)

(3)将代码放入方法并使用return.

var sum = 0
def findSum { for (i <- 0 to 1000) { sum += i; if (sum>=1000) return } }
findSum
Run Code Online (Sandbox Code Playgroud)

由于至少有三个我能想到的原因,故意这样做并不容易.首先,在大型代码块中,很容易忽略"继续"和"中断"语句,或者认为你的实际情况比实际情况更多或更少,或者需要打破两个你不能做的循环很容易 - 因此标准用法虽然方便,但却存在问题,因此您应该尝试以不同的方式构建代码.其次,Scala有各种各样的嵌套,你可能甚至都没有注意到,所以如果你能解决问题,你可能会对代码流的最终结束感到惊讶(尤其是关闭).第三,Scala的大多数"循环"实际上并不是正常循环 - 它们是具有自己的循环的方法调用,或者它们是递归,可能实际上也可能不是循环 - 尽管它们循环的,但它很难想出一个一致的方法来了解"休息"等应该做什么.所以,为了保持一致,更明智的做法就是不要有"休息".

注意:所有这些都具有功能等价物,您可以返回值sum而不是将其变异.这些是更惯用的Scala.但是,逻辑仍然是一样的.(return变得return x等).

  • @Jonathan - 如果你需要计算堆栈跟踪,异常只会很慢 - 请注意我是如何创建一个静态异常来抛出而不是动态生成一个异常!它们是一个完全有效的控制结构; 它们在整个Scala库中的多个位置使用,因为它们是您通过多种方法返回的唯一方法(如果您有一堆闭包,有时您需要这样做). (28认同)
  • @Rex Kerr,你指出了break结构的弱点(我不同意它们),但是你建议使用**例外**来进行正常的工作流程!退出循环不是例外情况,它是算法的一部分,而不是写入不存在的文件(例如).所以简而言之,"治愈"比"疾病"本身更糟糕.当我考虑在"易碎"部分抛出一个真正的例外......而所有那些箍都是为了避免邪恶的"破裂",嗯;-)你不得不承认,生活是具有讽刺意味的. (18认同)
  • @macias - 对不起,我的错误.JVM使用Throwables来控制流程.更好?仅仅因为它们通常用于支持异常处理并不意味着它们只能用于异常处理.从闭包内返回到定义的位置是_just like_就控制流而言抛出异常.因此,毫不奇怪,这是使用的机制. (17认同)
  • @RexKerr嗯,因为你值得信服我.通常情况下,我会成为一个针对正常程序流程的例外情况的人,但这两个主要原因并不适用.这些是:(1)它们很慢[不是以这种方式使用],以及(2)它们向读取代码的人提出了异常行为[如果你的图书馆让你称之为"休息",那就不一样了]如果它看起来像是一个`break`它表现得像一个'break`,就我而言它是一个'休息'. (14认同)
  • Re异常虽然严格来说可以抛出异常,但可以说是滥用异常机制(参见Effective Java).例外情况实际上是指出真正意外的情况和/或需要大量逃避代码,即某种错误.除此之外,它们当然非常慢(不确定当前情况)因为JVM没有理由优化它们. (9认同)
  • 您可以使用`.iterator`(2.7中的`.elements`)而不是`.toStream`来避免在内存中保存内容.我给出了每个例子,并不一定就你的确切情况指定最快的.(2)和(3)的基准是什么?他们慢多少? (3认同)
  • 我是否正确理解`for`体内的`return`被编译为`throw new SomeException`和`for` construct被放入try-catch中?因为我没有看到另一种方式如何实现. (3认同)
  • @macias - 使用`breakable`,然后. (2认同)
  • 在这些选项中,只有1a和1b与"中断"大不相同.示例2使用异常的方式与使用'break'的方式完全相同,只是使用额外的样板来定义异常类.示例3使用'return'的方式与'break'的使用方式完全相同,只是使用额外的样板来定义闭包.如果在循环中间很容易忽略"中断"或"继续",为什么单词'return'和'throw'在某种程度上神奇地不同? (2认同)

hoh*_*uli 64

这在Scala 2.8中有所改变,它具有使用休息的机制.您现在可以执行以下操作:

import scala.util.control.Breaks._
var largest = 0
// pass a function to the breakable method
breakable { 
    for (i<-999 to 1  by -1; j <- i to 1 by -1) {
        val product = i * j
        if (largest > product) {
            break  // BREAK!!
        }
        else if (product.toString.equals(product.toString.reverse)) {
            largest = largest max product
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 迈克:是的,Scala正在抛出异常以摆脱循环.Galder:这回答了已发布的问题"如何在Scala中突破循环?".它是"漂亮"还是不相关. (32认同)
  • 这是否在引擎盖下使用例外? (3认同)
  • @hohonuuli,所以它在try-catch块中不会中断,对吧? (2认同)
  • @GalderZamarreño为什么在这种情况下尾递归是一个优势?这不只是一种优化(谁的应用程序对于新手来说是隐藏的,而对于经验丰富的人却是令人困惑的应用程序)。在此示例中,尾部递归有什么好处吗? (2认同)

小智 29

打破for循环永远不是一个好主意.如果您使用for循环,则意味着您知道要迭代的次数.使用具有2个条件的while循环.

例如

var done = false
while (i <= length && !done) {
  if (sum > 1000) {
     done = true
  }
}
Run Code Online (Sandbox Code Playgroud)

  • 我认为这是打破Scala循环的正确方法。这个答案有什么问题吗?(考虑到较少的投票数)。 (2认同)

Pat*_*ick 13

要添加Rex Kerr,请回答另一种方法:

  • 我没有将它作为一个选项包含在内,因为它实际上没有打破循环 - 它遍历所有循环,但if语句在总和足够高之后的每次迭代都失败,因此它只执行一个if语句每次都值得工作.不幸的是,取决于你如何编写循环,这可能是很多工作. (30认同)
  • @MaciejPiechotka - JIT编译器通常不包含足够复杂的逻辑来识别变化变量的if语句将始终(在这种特殊情况下)返回false,因此可以省略. (5认同)

Vir*_*oop 8

我们在 scala 中可以做的就是

scala> import util.control.Breaks._

scala> object TestBreak {
       def main(args : Array[String]) {
         breakable {
           for (i <- 1 to 10) {
             println(i)
             if (i == 5)
               break;
       } } } }
Run Code Online (Sandbox Code Playgroud)

输出 :

scala> TestBreak.main(Array())
1
2
3
4
5
Run Code Online (Sandbox Code Playgroud)


Ham*_*cke 6

由于breakScala中还没有,你可以尝试使用return-statement 来解决这个问题.因此,您需要将内部循环放入函数中,否则返回将跳过整个循环.

然而,Scala 2.8包含了一种打破方式

http://www.scala-lang.org/api/rc/scala/util/control/Breaks.html

  • @TiansHUo:为什么你说Scala不喜欢_nested_循环?如果您试图突破_single_循环,则会遇到相同的问题. (4认同)

小智 5

// import following package
import scala.util.control._

// create a Breaks object as follows
val loop = new Breaks;

// Keep the loop inside breakable as follows
loop.breakable{
// Loop will go here
for(...){
   ....
   // Break will go here
   loop.break;
   }
}
Run Code Online (Sandbox Code Playgroud)

使用Break模块 http://www.tutorialspoint.com/scala/scala_break_statement.htm


pat*_*rit 5

只需使用while循环:

var (i, sum) = (0, 0)
while (sum < 1000) {
  sum += i
  i += 1
}
Run Code Online (Sandbox Code Playgroud)


elm*_*elm 5

一种方法,它在迭代时生成一个范围内的值,直到达到破坏条件为止,而不是先生成整个范围,然后使用进行迭代Iterator(在@RexKerr中得到启发Stream

var sum = 0
for ( i <- Iterator.from(1).takeWhile( _ => sum < 1000) ) sum += i
Run Code Online (Sandbox Code Playgroud)