¿Cómo salgo de un bucle en Scala?

276

¿Cómo rompo un bucle?

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

¿Cómo convierto los bucles anidados en recursión de cola?

De Scala Talk en FOSDEM 2009 http://www.slideshare.net/Odersky/fosdem-2009-1013261 en la página 22:

Romper y continuar Scala no los tiene. ¿Por qué? Son un poco imperativos; utilizar mejor muchas funciones más pequeñas. Emitir cómo interactuar con los cierres. ¡No son necesarios!

¿Cuál es la explicación?

TiansHUo
fuente
Su comparación necesita un segundo signo de igual: if (product.toString == product.toString.reverse) o tal vez una llamada de método igual.
usuario desconocido
sí, me perdí ese cuando lo estaba escribiendo
TiansHUo
Sé que estoy resucitando una vieja pregunta, pero me encantaría saber cuál es el propósito de este código. Primero pensé que estabas tratando de encontrar el producto "palíndromo" más grande posible con las combinaciones dadas de iy j. Si este código se completa sin salir del bucle, el resultado es que 906609al salir del bucle temprano, el resultado es 90909que romper el bucle no está haciendo que el código sea "más eficiente", ya que está alterando el resultado.
Ryan H.

Respuestas:

371

Tienes tres (más o menos) opciones para salir de los bucles.

Suponga que desea sumar números hasta que el total sea mayor que 1000. Intenta

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

excepto que quieres parar cuando (suma> 1000).

¿Qué hacer? Hay varias opciones

(1a) Use alguna construcción que incluya un condicional que pruebe.

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

(advertencia: esto depende de los detalles de cómo se intercalan la prueba takeWhile y el foreach durante la evaluación, ¡y probablemente no deberían usarse en la práctica!).

(1b) Use la recursión de cola en lugar de un bucle for, aprovechando lo fácil que es escribir un nuevo método en Scala:

var sum = 0
def addTo(i: Int, max: Int) {
  sum += i; if (sum < max) addTo(i+1,max)
}
addTo(0,1000)

(1c) Vuelva a utilizar un bucle while

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

(2) Lanzar una excepción.

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

(2a) En Scala 2.8+, esto ya está preempaquetado en el scala.util.control.Breaksuso de sintaxis que se parece mucho a su antiguo descanso familiar de C / Java:

import scala.util.control.Breaks._
var sum = 0
breakable { for (i <- 0 to 1000) {
  sum += i
  if (sum >= 1000) break
} }

(3) Ponga el código en un método y use return.

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

Esto se hace intencionalmente no demasiado fácil por al menos tres razones en las que puedo pensar. Primero, en bloques de código grandes, es fácil pasar por alto las declaraciones de "continuar" y "romper", o pensar que estás saliendo de más o menos de lo que realmente eres, o que necesitas romper dos bucles que no puedes hacer de todos modos fácilmente, por lo que el uso estándar, aunque práctico, tiene sus problemas y, por lo tanto, debe intentar estructurar su código de una manera diferente. En segundo lugar, Scala tiene todo tipo de anidamientos que probablemente ni siquiera note, por lo que si pudiera salir de las cosas, probablemente se sorprendería de dónde terminó el flujo del código (especialmente con los cierres). Tercero, la mayoría de los "bucles" de Scala no son en realidad bucles normales: son llamadas a métodos que tienen su propio bucle,como un bucle, es difícil encontrar una manera consistente de saber qué debe hacer "break" y cosas similares. Entonces, para ser consistente, lo más sabio es no tener un "descanso" en absoluto.

Nota : Hay equivalentes funcionales de todos estos en los que devuelve el valor en sumlugar de mutarlo en su lugar. Estos son Scala más idiomáticos. Sin embargo, la lógica sigue siendo la misma. (se returnconvierte return x, etc.).

Rex Kerr
fuente
9
Re excepciones, aunque es estrictamente cierto que puede lanzar una excepción, esto podría decirse que es un abuso del mecanismo de excepción (consulte Java efectivo). Las excepciones son realmente para indicar situaciones que son realmente inesperadas y / o requieren un escape drástico del código, es decir, errores de algún tipo. Aparte de eso, ciertamente solían ser bastante lentos (no estoy seguro de la situación actual) porque hay pocas razones para que las JVM los optimicen.
Jonathan
28
@Jonathan: las excepciones solo son lentas si necesita calcular un seguimiento de la pila: ¡observe cómo creé una excepción estática para lanzar en lugar de generar una sobre la marcha! Y son una construcción de control perfectamente válida; se usan en varios lugares en toda la biblioteca de Scala, ya que en realidad son la única forma en que puede regresar a través de múltiples métodos (que si tiene una pila de cierres es algo que a veces debe hacer).
Rex Kerr
18
@Rex Kerr, estás señalando las debilidades de la construcción de ruptura (no estoy de acuerdo con ellas), ¡pero luego sugieres usar excepciones para el flujo de trabajo normal! Salir de un bucle no es un caso excepcional, es parte del algoritmo, no es el caso de escribir en un archivo no existente (por ejemplo). En resumen, la "cura" sugerida es peor que la "enfermedad" misma. Y cuando considero lanzar una verdadera excepción en la breakablesección ... y todos esos aros solo para evitar el mal break, hmm ;-) Debes admitir que la vida es irónica.
greenoldman el
17
@macias - Lo siento, mi error. La JVM está utilizando Throwables para controlar el flujo. ¿Mejor? El hecho de que normalmente se usan para admitir el manejo de excepciones no significa que solo se puedan usar para el manejo de excepciones. Volver a una ubicación definida desde dentro de un cierre es como lanzar una excepción en términos de flujo de control. No sorprende, entonces, que este es el mecanismo que se utiliza.
Rex Kerr
14
@RexKerr Bueno, por lo que vale, me convenciste. Normalmente, sería uno de los que rechazaría las Excepciones para el flujo normal del programa, pero las dos razones principales no se aplican aquí. Esos son: (1) son lentos [no cuando se usan de esta manera], y (2) sugieren un comportamiento excepcional a alguien que lee su código [no si su biblioteca le permite llamarlos break] Si parece un breakay funciona como un break, en lo que a mí respecta, es un break.
Tim Goodman
66

Esto ha cambiado en Scala 2.8, que tiene un mecanismo para usar descansos. Ahora puede hacer lo siguiente:

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
        }
    }
}
hohonuuli
fuente
3
¿Utiliza esto excepciones bajo el capó?
Mike
Esto está usando Scala como un lenguaje de procedimiento que ignora las ventajas de programación funcional (es decir, la recursividad de cola). No es bonito.
Galder Zamarreño
32
Mike: Sí, Scala está lanzando una excepción para salir del círculo. Galder: Esto responde a la pregunta publicada "¿Cómo salgo de un bucle en Scala?". Si es "bonito" o no, no es relevante.
hohonuuli
2
@hohonuuli, por lo que está en el bloque try-catch no se romperá, ¿verdad?
greenoldman
2
@Galder Zamarreño ¿Por qué la recursividad de la cola es una ventaja en este caso? ¿No es simplemente una optimización (la aplicación de quién está oculta para el recién llegado y aplicada confusamente para el experimentado)? ¿Hay algún beneficio para la recursividad de la cola en este ejemplo?
user48956
33

Nunca es una buena idea salir de un ciclo for. Si está utilizando un bucle for, significa que sabe cuántas veces desea iterar. Use un bucle while con 2 condiciones.

por ejemplo

var done = false
while (i <= length && !done) {
  if (sum > 1000) {
     done = true
  }
}
Keith Blanchard
fuente
2
Esto es lo que siento es la forma correcta de salir de los bucles en Scala. ¿Hay algo malo con esta respuesta? (considerando el bajo número de votos a favor).
Jus12
1
de hecho simple y más legible. incluso lo rompible: la cosa de rotura es correcta, se ve fea y tiene problemas con el try-catch interno. Aunque su solución no funciona con un foreach, lo votaré, honrando la simplicidad.
yerlilbilgin
13

Para agregar Rex Kerr responda de otra manera:

  • (1c) También puedes usar un protector en tu bucle:

     var sum = 0
     for (i <- 0 to 1000 ; if sum<1000) sum += i
Patricio
fuente
30
No incluí esto como una opción porque en realidad no rompe el ciclo, lo ejecuta todo, pero la declaración if falla en cada iteración después de que la suma es lo suficientemente alta, por lo que solo hace una declaración if valor del trabajo cada vez. Desafortunadamente, dependiendo de cómo haya escrito el bucle, eso podría ser mucho trabajo.
Rex Kerr
@RexKerr: ¿El compilador no lo optimizaría de todos modos? ¿No se optimizará si no durante la primera ejecución y luego durante JIT?
Maciej Piechotka 01 de
55
@MaciejPiechotka: el compilador JIT generalmente no contiene una lógica lo suficientemente sofisticada como para reconocer que una declaración if en una variable cambiante siempre (en esta situación especial en particular) devolverá falso y, por lo tanto, puede omitirse.
Rex Kerr
6

Como todavía no existe breaken Scala, puede intentar resolver este problema con el uso de una returndeclaración. Por lo tanto, debe poner su bucle interno en una función; de lo contrario, el retorno omitirá todo el bucle.

Scala 2.8 sin embargo incluye una forma de romper

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

Jamón Vocke
fuente
lo siento, pero solo quería romper el circuito interno. ¿No estás insinuando que debería ponerlo en una función?
TiansHUo
Lo siento, debería haber aclarado eso. Claro que usar un retorno significa que necesita encapsular el bucle en una función. He editado mi respuesta.
Ham Vocke
1
Eso no es agradable en absoluto. Parece que a Scala no le gustan los bucles anidados.
TiansHUo
No parece haber una forma diferente. Es posible que desee echar un vistazo a esto: scala-lang.org/node/257
Ham Vocke
44
@TiansHUo: ¿Por qué dices que a Scala no le gustan los bucles anidados ? Tiene los mismos problemas si está tratando de salir de un solo ciclo.
Rex Kerr
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;
   }
}

use el módulo Break http://www.tutorialspoint.com/scala/scala_break_statement.htm

usuario1836270
fuente
5

Solo usa un ciclo while:

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

Un enfoque que genera los valores sobre un rango a medida que iteramos, hasta una condición de ruptura, en lugar de generar primero un rango completo y luego iterar sobre él, usando Iterator, (inspirado en el uso de @RexKerr de Stream)

var sum = 0
for ( i <- Iterator.from(1).takeWhile( _ => sum < 1000) ) sum += i
olmo
fuente
sí me gusta. sin excusa rompible, creo que se ve mejor.
ses
4

Aquí hay una versión recursiva de la cola. Comparado con las comprensiones, es un poco críptico, lo admito, pero diría que es funcional :)

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

Como puede ver, la función tr es la contrapartida de las comprensiones externas y tr1 de la interna. De nada si conoce una forma de optimizar mi versión.

Fresskoma
fuente
2

Cerca de su solución sería esto:

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)

La iteración j se realiza sin un nuevo alcance, y la generación del producto y la condición se realizan en la declaración for (no es una buena expresión, no encuentro una mejor). La condición se invierte, lo que es bastante rápido para ese tamaño de problema: tal vez ganes algo con un descanso para bucles más grandes.

String.reverse se convierte implícitamente en RichString, por lo que hago 2 reveses adicionales. :) Un enfoque más matemático podría ser más elegante.

usuario desconocido
fuente
2

El breakablepaquete de terceros es una alternativa posible.

https://github.com/erikerlandson/breakable

Código de ejemplo:

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))
eje
fuente
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.
   }
}

Método básico para romper el ciclo, usando la clase Breaks. Al declarar el bucle como rompible.

Parvathirajan Natarajan
fuente
2

Simplemente podemos hacer en scala es

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

salida:

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

Irónicamente, el allanamiento de Scala scala.util.control.Breakses una excepción:

def break(): Nothing = { throw breakException }

El mejor consejo es: ¡NO use break, continue y goto! En mi opinión, son lo mismo, una mala práctica y una fuente maligna de todo tipo de problemas (y discusiones candentes) y finalmente "se consideran perjudiciales". Bloque de código estructurado, también en este ejemplo los saltos son superfluos. Nuestro Edsger W. Dijkstra † escribió:

La calidad de los programadores es una función decreciente de la densidad de las declaraciones de ir a los programas que producen.

Epicurista
fuente
1

Tengo una situación como el código a continuación

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

Estoy usando una lib de Java y el mecanismo es que ctx.read produce una excepción cuando no puede encontrar nada. Estaba atrapado en la situación que: tengo que romper el ciclo cuando se lanzó una Excepción, pero scala.util.control.Breaks.break usando Excepción para romper el ciclo, y estaba en el bloque catch, por lo que fue capturado.

Tengo una forma fea de resolver esto: hacer el ciclo por primera vez y obtener el recuento de la longitud real. y úsalo para el segundo ciclo.

sacar un descanso de Scala no es tan bueno cuando estás usando algunas librerías de Java.

Lucas Liu
fuente
1

Soy nuevo en Scala, pero ¿qué tal esto para evitar lanzar excepciones y repetir métodos:

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

úsalo así:

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

si no quieres romper:

awhile(() => i < 20, () => {
    i = i + 1
    breakwhen(false)
});
Mohamad Alallan
fuente
1

El uso inteligente del findmétodo de recolección hará el truco por usted.

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)
AlvaPan
fuente
1

A continuación se muestra el código para romper un bucle de una manera simple

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"

        }
      }
    }
  }
}
Nilesh Shinde
fuente
1

No sé cuánto ha cambiado el estilo de Scala en los últimos 9 años, pero me pareció interesante que la mayoría de las respuestas existentes usen varso sean difíciles de leer. La clave para salir temprano es usar una colección perezosa para generar sus posibles candidatos, luego verifique la condición por separado. Para generar los productos:

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

Luego, para encontrar el primer palíndromo desde esa vista sin generar todas las combinaciones:

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

Para encontrar el palíndromo más grande (aunque la pereza no te compra mucho porque tienes que revisar la lista completa de todos modos):

palindromes.max

Su código original en realidad está verificando el primer palíndromo que es más grande que un producto posterior, que es lo mismo que verificar el primer palíndromo excepto en una condición límite extraña que no creo que haya querido. Los productos no disminuyen estrictamente monotónicamente. Por ejemplo, 998*998es mayor que 999*997, pero aparece mucho más tarde en los bucles.

De todos modos, la ventaja de la verificación de condición y generación perezosa separada es que la escribes como si usara la lista completa, pero solo genera la cantidad que necesitas. De alguna manera obtienes lo mejor de ambos mundos.

Karl Bielefeldt
fuente