在Scala中如何跳出循环?

我如何打破一个循环?

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循环转换为尾递归?

From Scala Talk at FOSDEM 2009 http://www.slideshare.net/Odersky/fosdem-2009-1013261 第22页:

中断继续 Scala没有它们。为什么? 它们有点迫不得已;最好使用许多较小的函数 发布如何与闭包交互。 他们是不需要的!< / p >

怎么解释呢?

242088 次浏览

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

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

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

您有三个(大约)选项可以跳出循环。

假设您希望将数字相加,直到总数大于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

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

请注意:在返回sum的值而不是原地改变它的地方,所有这些函数都有等价的函数。这些是更习惯的Scala。然而,逻辑是一样的。(return变成return x,等等)。

再加上雷克斯·科尔的另一种回答:

  • 你也可以在你的循环中使用一个守卫:

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

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

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语句中完成的(这不是一个好的表达式-我没有找到更好的表达式)。对于这个问题大小来说,条件是反向的,这是相当快的——也许对于更大的循环,您可以通过中断获得一些东西。

字符串。reverse隐式转换为RichString,这就是为什么我做了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
}
}
}

这是一个尾部递归的版本。不可否认,与for推导式相比,它有点晦涩,但我认为它是功能性的:)

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推导式的对应函数,tr1是内部for推导式的对应函数。如果你知道如何优化我的版本,欢迎你。

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

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

只需使用while循环:

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

具有讽刺意味的是,scala.util.control.Breaks中的Scala break是一个异常:

def break(): Nothing = { throw breakException }
最好的建议是:不要使用break, continue和goto!在我看来,它们是一样的,糟糕的做法和各种问题(和热议)的邪恶根源,最后“被认为是有害的”。代码块结构化,在本例中也有断点是多余的。 我们的Edsger W. Dijkstra†写道:

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

跳出for循环从来都不是一个好主意。如果你正在使用for循环,这意味着你知道你想要迭代多少次。使用带有两个条件的while循环。

例如

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

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

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

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

 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在找不到任何东西时抛出异常。 我被困在这样的情况中:当抛出异常时,我必须打破循环,但是scala.util.control. break .break使用异常来打破循环,并且它在catch块中,因此它被捕获 我有一个丑陋的方法来解决这个问题:第一次执行循环,并获得实际长度的计数。 然后在第二个循环中使用它

当你使用一些java库时,从Scala中解脱出来并不是那么好。

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

使用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)

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

.

.

在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

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

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"


}
}
}
}
}

我不知道Scala风格在过去的9年里发生了多大的变化,但我发现一个有趣的现象,即现有的大多数答案都使用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,但在循环中出现得更晚。

不管怎样,分离的惰性生成和条件检查的优点是,你写它的时候就像使用整个列表一样,但它只生成你需要的东西。你可以说是两全其美了。