如何在Scala中打破循环?


276

如何打破循环?

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
    }
}

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

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

中断并继续Scala没有它们。为什么?它们有点必要。更好地使用许多较小的函数发行如何与闭包进行交互。不需要它们!

有什么解释?


您的比较需要第二个等号:if(product.toString == product.toString.reverse)或一个equals-Method-call。
用户未知2010年

是的,我在输入时错过了它
TiansHUo 2010年

我知道我要复述一个老问题,但是我想知道这段代码的目的是什么?尽管我首先尝试使用给定的i和来找到最大的“回文”产品j。如果此代码在不中断循环的情况下运行完毕,则结果是,906609但通过尽早中断循环,结果就是90909中断循环不会使代码“更有效”,因为它会改变结果。
Ryan H.

Answers:


371

您有三个(或大约)选项可以打破循环。

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

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

除了要停止时(总和> 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

出于至少三个我能想到的原因,有意将其设置为不太容易。首先,在大型代码块中,很容易忽略“ continue”和“ break”语句,或者认为您超出了实际范围,或者需要打破两个您无法做到的循环无论如何都很容易-因此,标准用法虽然方便,但却存在问题,因此您应该尝试以其他方式构造代码。其次,Scala具有您甚至可能不会注意到的各种嵌套,因此,如果您遇到了麻烦,您可能会对代码流的结束位置(尤其是闭包)感到惊讶。第三,Scala的大多数“循环”实际上并不是正常的循环-它们是具有自己的循环的方法调用,像循环一样,很难找到一种一致的方式来知道“ break”之类的功能。因此,始终如一,明智的做法是根本不要“休息”。

注意:在所有这些函数中,都有等效的功能,您可以在其中返回值sum而不是对其进行适当的修改。这些是更惯用的Scala。但是,逻辑保持不变。(return变为return x等)。


9
关于异常,尽管严格说来可以抛出异常,但是可以说这是对异常机制的滥用(请参阅有效的Java)。异常实际上是表示确实出乎意料和/或需要从代码中进行大幅度转义的情况,即某种错误。除此之外,它们肯定曾经非常慢(不确定当前情况),因为JVM没有理由对其进行优化。
乔纳森

28
@Jonathan-异常仅在需要计算堆栈跟踪时才很慢-注意我如何创建一个静态异常以抛出而不是动态生成一个异常!它们是完全有效的控制结构;它们在整个Scala库中的多个地方都被使用,因为它们确实是您可以通过多种方法返回的唯一方法(如果您有一堆闭包,则有时需要这样做)。
雷克斯·克尔

18
@Rex Kerr,您指出了break构造的弱点(我不同意它们),但是随后您建议对常规工作流程使用异常!退出循环不是一种例外情况,它是算法的一部分,而不是写入不存在的文件的情况(例如)。简而言之,建议“治愈”比“疾病”本身差。当我考虑在breakable部分中抛出一个真正的例外……以及所有这些为避免邪恶的箍时break,嗯;-)你必须承认,生活具有讽刺意味。
greenoldman 2012年

17
@macias-对不起,我的错。JVM将Throwables用于控制流。更好?仅仅因为它们通常用于支持异常处理并不意味着它们只能用于异常处理。从闭包内部返回到定义的位置就像在控制流方面引发异常一样。那么,这就是所使用的机制也就不足为奇了。
Rex Kerr

14
@RexKerr好吧,值得您说服我。通常,对于正常的程序流程,我会坚决反对Exceptions,但是两个主要原因在这里并不适用。它们是:(1)它们很慢[不以这种方式使用时],以及(2)它们向阅读您的代码的人建议异常行为[不是如果您的库允许您调用它们的话break]如果它看起来像a break并且它执行像break,就我而言,这是一个break
Tim Goodman 2014年

66

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
        }
    }
}

3
这会在后台使用异常吗?
麦克,

这是使用Scala作为一种过程语言,而忽略了功能性编程的优势(即尾递归)。不漂亮。
2011年

32
迈克:是的,Scala抛出了异常以打破循环。Galder:这回答了发布的问题“如何在Scala中摆脱循环?”。是否“漂亮”无关紧要。
hohonuuli

2
@hohonuuli,所以它在try-catch块中不会中断,对吧?
greenoldman 2012年

2
@GalderZamarreño在这种情况下,为什么尾递归是一个优势?这不只是一种优化(谁的应用程序对新手来说是隐藏的,而对于经验丰富的人却是混乱的)。在此示例中,尾部递归有什么好处吗?
user48956 '16

32

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

例如

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

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

1
确实简单易读。即使是易碎的东西-打破事物也是正确的,它看起来很丑,并且在内部try-catch中存在问题。尽管您的解决方案无法与foreach一起使用,但我会投票支持您,以保持其简单性。
yerlilbilgin '16

13

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

  • (1c)您也可以在循环中使用防护装置:

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

30
我没有将此作为选项,因为它实际上并没有破坏循环-它贯穿了所有循环,但是在总和足够高之后,if语句在每次迭代时都会失败,因此它仅执行一个if语句每次值得的工作。不幸的是,取决于您编写循环的方式,这可能需要很多工作。
Rex Kerr 2010年

@RexKerr:编译器是否仍会对其进行优化?如果不是在第一次运行时而不是在JIT期间,它是否会被优化。
Maciej Piechotka'1

5
@MaciejPiechotka-JIT编译器通常不包含足够复杂的逻辑以识别正在变化的变量上的if语句将始终(在这种特殊情况下)返回false,因此可以省略。
Rex Kerr'1

6

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

但是,Scala 2.8包含一种破解方法

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


抱歉,但是我只想打破内在循环。您不是在暗示我应该将其放在函数中吗?
TiansHUo

抱歉,应该已经澄清了。确保使用return意味着您需要将循环封装在函数中。我已经编辑了答案。
汉·沃克

1
那根本不好。似乎Scala不喜欢嵌套循环。
TiansHUo

似乎没有什么不同的方式。您可能需要看一下:scala-lang.org/node/257
Ham Vocke

4
@TiansHUo:为什么要说Scala不喜欢嵌套循环?如果您试图摆脱单个循环,则会遇到相同的问题。
Rex Kerr


5

只需使用while循环:

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

5

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

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

是的,我喜欢它。没有可辩解的借口,我认为它看起来更好。
ses

4

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

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函数是外部理解的对应物,内部函数是tr1的对应物。如果您知道优化我的版本的方法,欢迎您。


2

与您的解决方案接近的是:

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次反向转换的原因。:)一种更数学的方法可能更优雅。


2

第三方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))

2
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类的打破循环的基本方法。通过声明循环为易碎的。


2

简单地说,我们可以在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

1

具有讽刺意味的是,Scala闯入scala.util.control.Breaks是一个例外:

def break(): Nothing = { throw breakException }

最好的建议是:请勿使用break,continue和goto!IMO认为它们是相同的,是错误的做法,是各种问题(和激烈的讨论)的邪恶根源,最终“被认为是有害的”。代码块结构化,在此示例中,中断也是多余的。我们的Edsger W. Dijkstra†写道:

程序员的素质是他们产生的程序中go语句密度降低的函数。


1

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

 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库,其机制是ctx.read在找不到任何内容时抛出异常。我被困在这样一种情况::抛出异常时必须中断循环,但是使用Exception来中断循环的scala.util.control.Breaks.break却在catch块中,因此被捕获了。

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

当您使用一些Java库时,从Scala中脱颖而出并不是很好。


1

我是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)
});

1

聪明地使用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)

1

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

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"

        }
      }
    }
  }
}

1

我不知道在过去的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,但在循环中出现得更晚。

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.