Как мне выйти из цикла в Scala? - PullRequest
259 голосов
/ 30 апреля 2010

Как мне разорвать петлю?

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

Как превратить вложенные циклы в хвостовую рекурсию?

Из Scala Talk на FOSDEM 2009 http://www.slideshare.net/Odersky/fosdem-2009-1013261 на 22 странице:

Перерыв и продолжить У Скалы их нет. Зачем? Они немного необходимы; лучше использовать много мелких функций Вопрос, как взаимодействовать с замыканиями. Они не нужны!

Какое объяснение?

Ответы [ 18 ]

356 голосов
/ 30 апреля 2010

У вас есть три (или около того) варианта выхода из циклов.

Предположим, вы хотите суммировать числа, пока сумма не станет больше 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 на самом деле не являются обычными циклами - это вызовы методов, которые имеют свой собственный цикл, или они являются рекурсией, которая может на самом деле быть циклом, а может и не быть - и хотя они act петлеобразный, трудно придумать последовательный способ узнать, что должен делать «перерыв» и тому подобное. Поэтому, чтобы быть последовательным, мудрее всего не делать «перерыв».

Примечание : есть функциональные эквиваленты всего этого, где вы возвращаете значение sum, а не изменяете его на месте. Это более идиоматические Scala. Однако логика остается прежней. (return становится return x и т. Д.).

60 голосов
/ 28 февраля 2011

Это изменилось в 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
        }
    }
}
29 голосов
/ 02 августа 2014

Никогда не стоит выходить из цикла. Если вы используете цикл for, это означает, что вы знаете, сколько раз вы хотите выполнить итерацию. Используйте цикл while с 2 условиями.

например

var done = false
while (i <= length && !done) {
  if (sum > 1000) {
     done = true
  }
}
12 голосов
/ 30 апреля 2010

Чтобы добавить Рекс Керр, ответьте по-другому:

  • (1с) Вы также можете использовать охрану в своей петле:

     var sum = 0
     for (i <- 0 to 1000 ; if sum<1000) sum += i
    
6 голосов
/ 30 апреля 2010

Так как в Scala еще нет break, вы можете попытаться решить эту проблему с помощью return -статации.Поэтому вам нужно поместить свой внутренний цикл в функцию, иначе возвращение пропустит весь цикл.

Scala 2.8, однако, содержит способ разорвать

http://www.scala -языкорг / API / гс / Scala / Util / контроль / Breaks.html

5 голосов
/ 22 апреля 2014

Просто используйте цикл while:

var (i, sum) = (0, 0)
while (sum < 1000) {
  sum += i
  i += 1
}
5 голосов
/ 10 марта 2014
// 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

4 голосов
/ 12 февраля 2015

Подход, который генерирует значения в диапазоне во время итерации, вплоть до условия прерывания, вместо генерации сначала целого диапазона, а затем итерации по нему, используя Iterator, (вдохновленный @RexKerr использованием Stream )

var sum = 0
for ( i <- Iterator.from(1).takeWhile( _ => sum < 1000) ) sum += i
4 голосов
/ 06 июня 2011

Вот хвостовая рекурсивная версия. По сравнению с «для понимания» это немного загадочно, правда, но я бы сказал, его функционал:)

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 голосов
/ 20 июля 2018

Просто мы можем сделать в 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
...