如何在 Scala 中跳出循环?

Posted

技术标签:

【中文标题】如何在 Scala 中跳出循环?【英文标题】:How do I break out of a loop in Scala? 【发布时间】:2011-02-14 02:57:55 【问题描述】:

如何打破循环?

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
    

如何将嵌套的 for 循环变成尾递归?

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

中断并继续 Scala 没有它们。为什么? 它们有点势在必行。更好地使用许多较小的功能 问题如何与闭包交互。 不需要它们!

解释是什么?

【问题讨论】:

您的比较需要第二个等号:if(product.toString == product.toString.reverse) 或者可能是等号方法调用。 是的,我在输入时错过了那个 我知道我正在复活一个老问题,但我很想知道这段代码的目的是什么?我首先认为您试图通过ij 的给定组合找到最大的“回文”产品。如果此代码在没有跳出循环的情况下运行完成,则结果为906609,但通过提前跳出循环,结果为90909,因此跳出循环不会使代码“更高效”为它正在改变结果。 【参考方案1】:

您有三个(左右)选项可以跳出循环。

假设您想对数字求和,直到总数大于 1000。您尝试

var sum = 0
for (i <- 0 to 1000) sum += i

除非你想在 (sum > 1000) 时停止。

怎么办?有几种选择。

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

var sum = 0
(0 to 1000).iterator.takeWhile(_ => sum < 1000).foreach(i => sum+=i)

(警告——这取决于 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)

(1c) 回退到使用 while 循环

var sum = 0
var i = 0
while (i <= 1000 && sum <= 1000)  sum += 1; i += 1 

(2) 抛出异常。

object AllDone extends Exception  
var sum = 0
try 
  for (i <- 0 to 1000)  sum += i; if (sum>=1000) throw AllDone 
 catch 
  case AllDone =>

(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
 

(3)将代码放入方法中,使用return。

var sum = 0
def findSum  for (i <- 0 to 1000)  sum += i; if (sum>=1000) return  
findSum

出于我能想到的至少三个原因,故意使这不太容易。首先,在大型代码块中,很容易忽略“继续”和“中断”语句,或者认为您的中断比实际情况多或少,或者需要中断两个无法执行的循环无论如何都很容易——因此标准用法虽然很方便,但有其问题,因此您应该尝试以不同的方式构建代码。其次,Scala 有各种各样的嵌套,你可能根本没有注意到,所以如果你能打破这些东西,你可能会对代码流的最终位置感到惊讶(尤其是闭包)。第三,Scala 的大多数“循环”实际上并不是普通的循环——它们是具有自己循环的方法调用,或者它们是可能实际上是也可能不是循环的递归——尽管它们 act looplike,很难想出一个一致的方法来知道“break”之类的应该做什么。因此,为了保持一致,更明智的做法是根本不“休息”。

注意:在所有这些功能等价物的情况下,您可以返回 sum 的值,而不是在适当的位置对其进行变异。这些是更惯用的Scala。但是,逻辑保持不变。 (return 变为 return x 等)。

【讨论】:

Re 异常,虽然严格来说可以抛出异常,但这可以说是对异常机制的滥用(参见 Effective Java)。异常实际上是为了表明真正出乎意料的情况和/或需要从代码中彻底逃脱的情况,即某种错误。除此之外,它们过去确实很慢(不确定当前的情况),因为 JVM 几乎没有理由优化它们。 @Jonathan - 如果您需要计算堆栈跟踪,异常只会很慢 - 请注意我是如何创建一个静态异常来抛出而不是动态生成的!它们是一个完全有效的控制结构;它们在整个 Scala 库中的多个地方使用,因为它们确实是您可以通过多种方法返回的唯一方法(如果您有一堆闭包,您有时需要这样做)。 @Rex Kerr,您指出了 break 结构的弱点(我不同意他们的观点),但是您建议将 exceptions 用于正常工作流程!退出循环不是例外情况,它是算法的一部分,而不是写入不存在的文件(例如)。所以简而言之,建议“治疗”比“疾病”本身更糟糕。当我考虑在breakable 部分抛出一个真正的异常时......所有这些都是为了避免邪恶break,嗯;-) 你必须承认,生活很讽刺。 @macias - 对不起,我的错误。 JVM 使用 Throwables 进行控制流。更好的?仅仅因为它们通常用于支持异常处理并不意味着它们只能用于异常处理。从闭包中返回到定义的位置就像在控制流方面抛出异常。那么,这就是所使用的机制也就不足为奇了。 @RexKerr 好吧,值得你说服我。通常我会反对正常程序流程的异常,但这两个主要原因在这里不适用。它们是:(1)它们很慢[不是以这种方式使用],并且(2)它们向阅读您的代码的人建议异常行为[如果您的库允许您调用它们break,则不会)如果它看起来像breakbreak 一样,就我而言,它是 break【参考方案2】:

这在 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
        
    

【讨论】:

这是否在后台使用异常? 这是使用 Scala 作为过程语言,忽略了函数式编程的优势(即尾递归)。不漂亮。 Mike:是的,Scala 正在抛出异常以跳出循环。 Galder:这回答了已发布的问题“如何在 Scala 中跳出循环?”。是否“漂亮”无关紧要。 @hohonuuli,所以它在 try-catch 块中不会中断,对吧? @Galder Zamarreño 为什么尾递归在这种情况下是一个优势?不就是简单的优化吗(谁的应用对新手来说是隐藏的,而对于有经验的人来说应用是混乱的)。在这个例子中,尾递归有什么好处吗?【参考方案3】:

跳出for循环从来都不是一个好主意。如果您使用的是 for 循环,则意味着您知道要迭代多少次。使用具有 2 个条件的 while 循环。

例如

var done = false
while (i <= length && !done) 
  if (sum > 1000) 
     done = true
  

【讨论】:

这是我认为在 Scala 中打破循环的正确方法。这个答案有什么问题吗? (考虑到赞成票的数量很少)。 确实简单易读。即使是易碎的——break thingy 也是正确的,它看起来很丑,并且在内部 try-catch 中存在问题。尽管您的解决方案不适用于 foreach,但我会为您投票,以尊重其简单性。【参考方案4】:

要添加 Rex Kerr 以另一种方式回答:

(1c) 你也可以在你的循环中使用一个守卫:

 var sum = 0
 for (i <- 0 to 1000 ; if sum<1000) sum += i

【讨论】:

我没有把它作为一个选项包括在内,因为它实际上并没有打破循环——它贯穿了所有循环,但是在总和足够高之后,if 语句在每次迭代中都会失败,所以它每次只做一个 if 语句的工作。不幸的是,根据您编写循环的方式,这可能需要大量工作。 @RexKerr:编译器不会优化它吗?如果不是在第一次运行期间然后在 JIT 期间,它不会被优化吗? @MaciejPiechotka - JIT 编译器通常不包含足够复杂的逻辑来识别变化变量上的 if 语句将始终(在这种特殊情况下)返回 false,因此可以省略。 【参考方案5】:

由于 Scala 中还没有 break,您可以尝试使用 return 语句来解决这个问题。因此你需要将你的内部循环放入一个函数中,否则返回会跳过整个循环。

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

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

【讨论】:

抱歉,我只想打破内部循环。你不是在暗示我应该把它放在一个函数中吗? 对不起,应该澄清一下。确定使用返回意味着您需要将循环封装在一个函数中。我已经编辑了我的答案。 那一点都不好。看来 Scala 不喜欢嵌套循环。 似乎没有不同的方式。你可能想看看这个:scala-lang.org/node/257 @TiansHUo:为什么说Scala不喜欢嵌套循环?如果您尝试跳出 single 循环,也会遇到同样的问题。【参考方案6】:

一种在我们迭代时生成一个范围内的值的方法,直到一个中断条件,而不是首先生成一个整个范围然后迭代它,使用Iterator,(灵感来自@RexKerr 使用Stream )

var sum = 0
for ( i <- Iterator.from(1).takeWhile( _ => sum < 1000) ) sum += i

【讨论】:

是的,我喜欢。没有易碎的借口,我认为它看起来更好。【参考方案7】:

我们可以在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;
          

输出:

scala> TestBreak.main(Array())
1
2
3
4
5

【讨论】:

【参考方案8】:
// 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;
   

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

【讨论】:

【参考方案9】:

只需使用一个while循环:

var (i, sum) = (0, 0)
while (sum < 1000) 
  sum += i
  i += 1

【讨论】:

【参考方案10】:

这是一个尾递归版本。诚然,与理解相比,它有点神秘,但我想说它的功能:)

def run(start:Int) = 
  @tailrec
  def tr(i:Int, largest:Int):Int = tr1(i, i, largest) match 
    case x if i > 1 => tr(i-1, x)
    case _ => largest
  

  @tailrec
  def tr1(i:Int,j:Int, largest:Int):Int = i*j match 
    case x if x < largest || j < 2 => largest
    case x if x.toString.equals(x.toString.reverse) => tr1(i, j-1, x)
    case _ => tr1(i, j-1, largest)
  

  tr(start, 0)

如您所见,tr 函数是外部 for-comprehensions 的对应物,而 tr1 是内部 for-comprehensions 的对应物。如果您知道优化我的版本的方法,欢迎您。

【讨论】:

【参考方案11】:

接近你的解决方案是这样的:

var largest = 0
for (i <- 999 to 1 by -1;
  j <- i to 1 by -1;
  product = i * j;
  if (largest <= product && product.toString.reverse.equals (product.toString.reverse.reverse)))
    largest = product

println (largest)

j 迭代是在没有新范围的情况下进行的,产品生成和条件是在 for 语句中完成的(不是一个好的表达方式 - 我找不到更好的表达方式)。条件是相反的,这对于该问题的大小来说是相当快的——也许你可以通过中断更大的循环来获得一些东西。

String.reverse 隐式转换为 RichString,这就是我做 2 次额外反转的原​​因。 :) 更数学的方法可能更优雅。

【讨论】:

【参考方案12】:

我是 Scala 新手,但是如何避免抛出异常和重复方法:

object awhile 
def apply(condition: () => Boolean, action: () => breakwhen): Unit = 
    while (condition()) 
        action() match 
            case breakwhen(true)    => return ;
            case _                  =>  ;
        
    

case class breakwhen(break:Boolean);

像这样使用它:

var i = 0
awhile(() => i < 20, () => 
    i = i + 1
    breakwhen(i == 5)
);
println(i)

如果你不想打破:

awhile(() => i < 20, () => 
    i = i + 1
    breakwhen(false)
);

【讨论】:

【参考方案13】:

第三方breakable 包是一种可能的替代方案

https://github.com/erikerlandson/breakable

示例代码:

scala> import com.manyangled.breakable._
import com.manyangled.breakable._

scala> val bkb2 = for 
     |   (x, xLab) <- Stream.from(0).breakable   // create breakable sequence with a method
     |   (y, yLab) <- breakable(Stream.from(0))  // create with a function
     |   if (x % 2 == 1) continue(xLab)          // continue to next in outer "x" loop
     |   if (y % 2 == 0) continue(yLab)          // continue to next in inner "y" loop
     |   if (x > 10) break(xLab)                 // break the outer "x" loop
     |   if (y > x) break(yLab)                  // break the inner "y" loop
     |  yield (x, y)
bkb2: com.manyangled.breakable.Breakable[(Int, Int)] = com.manyangled.breakable.Breakable@34dc53d2

scala> bkb2.toVector
res0: Vector[(Int, Int)] = Vector((2,1), (4,1), (4,3), (6,1), (6,3), (6,5), (8,1), (8,3), (8,5), (8,7), (10,1), (10,3), (10,5), (10,7), (10,9))

【讨论】:

【参考方案14】:
import scala.util.control._

object demo_brk_963 

   def main(args: Array[String]) 
   
      var a = 0;
      var b = 0;
      val numList1 = List(1,2,3,4,5,6,7,8,9,10);
      val numList2 = List(11,12,13);

      val outer = new Breaks; //object for break
      val inner = new Breaks; //object for break

      outer.breakable // Outer Block
      
         for( a <- numList1)
         
            println( "Value of a: " + a);

            inner.breakable // Inner Block
            
               for( b <- numList2)
               
                  println( "Value of b: " + b);

                  if( b == 12 )
                  
                      println( "break-INNER;");
                       inner.break;
                  
               
             // inner breakable
            if( a == 6 )
            
                println( "break-OUTER;");
                outer.break;
            
         
       // outer breakable.
   

使用 Breaks 类打破循环的基本方法。 通过将循环声明为可破坏的。

【讨论】:

【参考方案15】:

讽刺的是,scala.util.control.Breaks 中的 Scala 中断是一个例外:

def break(): Nothing =  throw breakException 

最好的建议是:不要使用 break、continue 和 goto! IMO 他们是相同的,不好的做法和各种问题(和热门讨论)的邪恶来源,最终“被认为是有害的”。代码块结构化,在这个例子中中断也是多余的。 我们的 Edsger W. Dijkstra† 写道:

程序员的素质是他们编写的程序中 go to 语句密度的递减函数。

【讨论】:

【参考方案16】:

我遇到了类似下面代码的情况

 for(id<-0 to 99) 
    try 
      var symbol = ctx.read("$.stocks[" + id + "].symbol").toString
      var name = ctx.read("$.stocks[" + id + "].name").toString
      stocklist(symbol) = name
    catch 
      case ex: com.jayway.jsonpath.PathNotFoundException=>break
    
  

我正在使用 java lib,其机制是 ctx.read 在找不到任何内容时抛出异常。 我被困在这样的情况下:当抛出异常时我必须打破循环,但是 scala.util.control.Breaks.break 使用 Exception 打破循环,并且它在 catch 块中,因此它被捕获了。

我有一个丑陋的方法来解决这个问题:第一次执行循环并获取实际长度的计数。 并将其用于第二个循环。

当你使用一些 java 库时,从 Scala 中取出休息并不是那么好。

【讨论】:

【参考方案17】:

聪明使用find 方法进行收集将为您解决问题。

var largest = 0
lazy val ij =
  for (i <- 999 to 1 by -1; j <- i to 1 by -1) yield (i, j)

val largest_ij = ij.find  case(i,j) =>
  val product = i * j
  if (product.toString == product.toString.reverse)
    largest = largest max product
  largest > product


println(largest_ij.get)
println(largest)

【讨论】:

【参考方案18】:

下面是用简单的方式打破循环的代码

import scala.util.control.Breaks.break

object RecurringCharacter 
  def main(args: Array[String]) 
    val str = "nileshshinde";

    for (i <- 0 to str.length() - 1) 
      for (j <- i + 1 to str.length() - 1) 

        if (str(i) == str(j)) 
          println("First Repeted Character " + str(i))
          break()     //break method will exit the loop with an Exception "Exception in thread "main" scala.util.control.BreakControl"

        
      
    
  

【讨论】:

【参考方案19】:

我不知道过去 9 年 Scala 风格发生了多大的变化,但我发现有趣的是,大多数现有答案都使用 vars,或者难以阅读的递归。提前退出的关键是使用惰性集合来生成可能的候选者,然后单独检查条件。生成产品:

val products = for 
  i <- (999 to 1 by -1).view
  j <- (i to 1 by -1).view
 yield (i*j)

然后从该视图中找到第一个回文而不生成每个组合:

val palindromes = products filter p => p.toString == p.toString.reverse
palindromes.head

要找到最大的回文(虽然懒惰并没有给你带来太多好处,因为无论如何你都必须检查整个列表):

palindromes.max

您的原始代码实际上是检查大于后续产品的第一个回文,这与检查第一个回文相同,除了我认为您不打算的奇怪边界条件。产品不是严格单调递减的。例如,998*998 大于 999*997,但在循环中出现得更晚。

无论如何,分离的惰性生成和条件检查的优点是您编写它就像使用整个列表一样,但它只生成您需要的数量。你可以两全其美。

【讨论】:

以上是关于如何在 Scala 中跳出循环?的主要内容,如果未能解决你的问题,请参考以下文章

在JAVA中如何跳出当前的多重嵌套循环?

在Java中,如何跳出当前的多重嵌套循环?

在java中,如何跳出当前的嵌套循环

Java中如何跳出多重循环?

mssql 存储过程中循环如何写,在循环中用啥语句跳出循环呢,在线等

如何使用python语句跳出循环