如何摆脱Scala中的循环?

时间:2010-04-30 06:34:08

标签: scala for-loop break tail-recursion

如何打破循环?

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演讲http://www.slideshare.net/Odersky/fosdem-2009-1013261 在第22页:

  

休息并继续   Scala没有它们。为什么?   他们有点必要;更好地使用许多小功能   发出如何与闭包交互。   他们不需要!

解释是什么?

19 个答案:

答案 0 :(得分:355)

你有三个(左右)选项可以摆脱循环。

假设您要对总数大于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

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

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

答案 1 :(得分:60)

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

答案 2 :(得分:29)

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

例如

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

答案 3 :(得分:12)

以另一种方式添加Rex Kerr:

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

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

答案 4 :(得分:6)

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

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

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

答案 5 :(得分: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;
   }
}

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

答案 6 :(得分:5)

只需使用while循环:

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

答案 7 :(得分:4)

这是一个尾递归版本。与for-comprehensions相比,它确实有点神秘,但我会说它的功能:)

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

答案 8 :(得分:4)

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

Stream

答案 9 :(得分: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-iteration在没有新范围的情况下进行,产品生成以及条件都在for语句中完成(不是一个好的表达式 - 我找不到更好的表达式)。条件是相反的,对于那个问题大小来说相当快 - 也许你可以通过更大的循环来获得一些东西。

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

答案 10 :(得分: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))

答案 11 :(得分: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

答案 12 :(得分:1)

具有讽刺意味的是,scala.util.control.Breaks中的Scala中断是个例外:

def break(): Nothing = { throw breakException }

最好的建议是:不要使用休息,继续并转到! IMO他们是相同的,坏的做法和各种问题的邪恶来源(和热门的讨论),最后被认为是有害的&#34;。代码块结构化,在这个示例中,中断也是多余的。 我们的Edsger W. Dijkstra†写道:

  

程序员的素质是他们制作的程序中语句密度的递减函数。

答案 13 :(得分: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)

答案 14 :(得分:1)

  

下面是简单中断代码的代码

.python_history

答案 15 :(得分: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,但出现在循环中要晚得多。

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

答案 16 :(得分:0)

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

 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块中因此被捕获。 / p>

我得到了解决这个问题的难看方法:第一次进行循环并获得实际长度的计数。 并将其用于第二个循环。

当你使用一些java库时,

从Scala中取出并不是那么好。

答案 17 :(得分:0)

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

答案 18 :(得分:0)

def getStatus():
    while True:
        answer = input('What is the box ID? ')
        if answer == 999:
            break
        elif isinstance(answer, int): # isinsance() is better than type(...) == ...
            boxId = answer + 1
            print(boxId)
        else:
            print("I'm sorry, I didn't understand that.") # still good to handle this case

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