Come faccio a uscire da un ciclo in Scala?


276

Come faccio a interrompere un ciclo?

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

Come posso trasformare i nidificati per loop in ricorsione della coda?

Da Scala Talk al FOSDEM 2009 http://www.slideshare.net/Odersky/fosdem-2009-1013261 alla 22a pagina:

Rompere e continuare Scala non li ha. Perché? Sono un po 'imperativi; utilizzare meglio molte funzioni più piccole Emettere come interagire con le chiusure. Non sono necessari!

Qual è la spiegazione?


Il confronto ha bisogno di un secondo segno di uguale: if (product.toString == product.toString.reverse) o forse una chiamata a metodo uguale.
utente sconosciuto

sì, mi mancava quello mentre lo
scrivevo

So che sto resuscitando una vecchia domanda, ma mi piacerebbe sapere qual è lo scopo di questo codice? Prima ho pensato che stavi cercando di trovare il più grande prodotto "palindromo" possibile con le combinazioni date di ie j. Se questo codice viene eseguito fino al completamento senza uscire dal ciclo, il risultato è 906609ma interrompendo il ciclo in anticipo, il risultato è 90909che uscire dal ciclo non sta rendendo il codice "più efficiente" in quanto altera il risultato.
Ryan H.

Risposte:


371

Hai tre (o giù di lì) opzioni per uscire dai loop.

Supponiamo di voler sommare i numeri fino a quando il totale è maggiore di 1000. Prova

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

tranne che si desidera interrompere quando (somma> 1000).

Cosa fare? Esistono diverse opzioni.

(1a) Utilizzare un costrutto che include un condizionale testato.

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

(attenzione - questo dipende dai dettagli di come il test takeWhile e il foreach sono interlacciati durante la valutazione e probabilmente non dovrebbero essere usati in pratica!).

(1b) Usa la ricorsione della coda invece di un ciclo for, approfittando di quanto sia facile scrivere un nuovo metodo in Scala:

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

(1c) Riporta all'utilizzo di un ciclo while

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

(2) Genera un'eccezione.

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

(2a) In Scala 2.8+ questo è già preconfezionato scala.util.control.Breaksnell'uso della sintassi che assomiglia molto alla tua vecchia pausa familiare da C / Java:

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

(3) Inserisci il codice in un metodo e usa return.

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

Questo è intenzionalmente reso non troppo facile per almeno tre motivi a cui riesco a pensare. Innanzitutto, in blocchi di codice di grandi dimensioni, è facile trascurare le istruzioni "continue" e "break", o pensare che stai uscendo da più o meno di quello che sei realmente, o devi interrompere due loop che non puoi fare facilmente comunque - quindi l'uso standard, sebbene utile, ha i suoi problemi, e quindi dovresti provare a strutturare il tuo codice in un modo diverso. In secondo luogo, Scala ha molti tipi di annidamenti che probabilmente non noterai nemmeno, quindi se potessi uscire dalle cose, probabilmente rimarrai sorpreso da dove finiva il flusso del codice (specialmente con le chiusure). Terzo, la maggior parte dei "loop" di Scala non sono in realtà loop normali: sono chiamate di metodo che hanno il loro loop,looplike, è difficile trovare un modo coerente per sapere cosa dovrebbe fare "break" e simili. Quindi, per essere coerenti, la cosa più saggia da fare è non avere affatto una "pausa".

Nota : ci sono equivalenti funzionali di tutti questi in cui si restituisce il valore sumpiuttosto che mutarlo in posizione. Questi sono Scala più idiomatica. Tuttavia, la logica rimane la stessa. ( returndiventa return x, ecc.).


9
Ri-eccezioni, sebbene sia assolutamente vero che è possibile generare un'eccezione, si tratta probabilmente di un abuso del meccanismo delle eccezioni (vedere Java efficace). Le eccezioni indicano davvero situazioni che sono davvero inaspettate e / o richiedono una drastica fuga dal codice, ovvero errori di qualche tipo. A parte questo, erano certamente piuttosto lenti (non sono sicuro della situazione attuale) perché le JVM hanno poche ragioni per ottimizzarle.
Jonathan,

28
@Jonathan - Le eccezioni sono lente solo se devi calcolare una traccia dello stack - nota come ho creato un'eccezione statica da lanciare invece di generarne una al volo! E sono un costrutto di controllo perfettamente valido; sono usati in più punti della biblioteca Scala, dato che sono davvero l'unico modo in cui puoi tornare attraverso più metodi (che se hai una pila di chiusure è qualcosa che a volte devi fare).
Rex Kerr,

18
@Rex Kerr, stai sottolineando i punti deboli del costrutto break (non sono d'accordo con loro), ma poi suggerisci di usare le eccezioni per il normale flusso di lavoro! Uscire da un ciclo non è un caso eccezionale, fa parte dell'algoritmo, non è il caso di scrivere su file inesistenti (ad esempio). Quindi in breve la "cura" suggerita è peggiore della "malattia" stessa. E quando penso di lanciare una vera eccezione nella breakablesezione ... e tutti quei cerchi solo per evitare il male break, hmm ;-) Devi ammettere, la vita è ironica.
Greenoldman,

17
@macias - Scusa, mio ​​errore. La JVM utilizza Throwables per il flusso di controllo. Meglio? Solo perché sono in genere utilizzati per supportare la gestione delle eccezioni non significa che possono essere utilizzati solo per la gestione delle eccezioni. Tornare in una posizione definita all'interno di una chiusura è come lanciare un'eccezione in termini di flusso di controllo. Nessuna sorpresa, quindi, che questo è il meccanismo che viene utilizzato.
Rex Kerr,

14
@RexKerr Beh, per quello che vale mi hai convinto. Normalmente sarei uno a favore delle eccezioni per il normale flusso di programma, ma i due motivi principali non si applicano qui. Quelli sono: (1) sono lenti [non usati in questo modo], e (2) suggeriscono un comportamento eccezionale a qualcuno che legge il tuo codice [non se la tua libreria ti consente di chiamarli break] Se sembra un breake si comporta come a break, per quanto mi riguarda è a break.
Tim Goodman,

66

Questo è cambiato in Scala 2.8 che ha un meccanismo per usare le pause. Ora puoi fare quanto segue:

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
Questo utilizza eccezioni sotto il cofano?
Mike,

Questo sta usando Scala come linguaggio procedurale ignorando i vantaggi della programmazione funzionale (es. Ricorsione della coda). Non carino.
Galder Zamarreño,

32
Mike: Sì, Scala lancia un'eccezione per uscire dal giro. Galder: Questo risponde alla domanda postata "Come faccio ad uscire da un loop in Scala?". Che sia "carino" o no non è rilevante.
hohonuuli,

2
@hohonuuli, quindi è nel blocco try-catch che non si romperà, giusto?
Greenoldman,

2
@Galder Zamarreño Perché la ricorsione della coda è un vantaggio in questo caso? Non è semplicemente un'ottimizzazione (la cui applicazione è nascosta per il nuovo arrivato e applicata in modo confuso per l'esperto). Ci sono dei vantaggi per la ricorsione della coda in questo esempio?
user48956,

33

Non è mai una buona idea uscire da un for-loop. Se stai usando un for-loop significa che sai quante volte vuoi iterare. Utilizzare un ciclo while con 2 condizioni.

per esempio

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

2
Questo è quello che sento sia il modo giusto di uscire dagli anelli della Scala. C'è qualcosa di sbagliato in questa risposta? (considerando il basso numero di voti positivi).
Jus12

1
davvero semplice e più leggibile. anche il breakable - break thingy è corretto, sembra brutto e ha problemi con il try-catch interno. Anche se la tua soluzione non funziona con una foreach, ti voterò, onorando la semplicità.
yerlilbilgin,

13

Per aggiungere la risposta di Rex Kerr in un altro modo:

  • (1c) Puoi anche usare una guardia nel tuo loop:

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

30
Non l'ho incluso come opzione perché in realtà non interrompe il ciclo - scorre tutto, ma l'istruzione if fallisce su ogni iterazione dopo che la somma è abbastanza alta, quindi fa solo un'istruzione if lavoro ogni volta. Sfortunatamente, a seconda di come hai scritto il ciclo, potrebbe essere un sacco di lavoro.
Rex Kerr,

@RexKerr: il compilatore non lo ottimizzerebbe comunque? Non sarà ottimizzato se non durante la prima esecuzione, quindi durante JIT.
Maciej Piechotka,

5
@MaciejPiechotka - Il compilatore JIT generalmente non contiene una logica sufficientemente sofisticata per riconoscere che un'istruzione if su una variabile che cambia restituirà sempre (in questa particolare situazione speciale) restituire false e quindi può essere omessa.
Rex Kerr

6

Dato che non esiste ancora breakin Scala, potresti provare a risolvere questo problema usando una returndichiarazione. Pertanto è necessario inserire il proprio ciclo interno in una funzione, altrimenti il ​​ritorno salta l'intero ciclo.

Scala 2.8 tuttavia include un modo per rompere

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


scusa, ma volevo solo interrompere il circuito interno. Non stai insinuando che dovrei metterlo in funzione?
TiansHoo

Spiacente, avrei dovuto chiarirlo. Sicuramente l'uso di un ritorno significa che è necessario incapsulare il loop in una funzione. Ho modificato la mia risposta.
Ham Vocke

1
Non è affatto carino. Sembra che alla Scala non piacciano i loop nidificati.
TiansHoo

Non sembra esserci un modo diverso. Potresti dare un'occhiata a questo: scala-lang.org/node/257
Ham Vocke

4
@TiansHUo: Perché dici che alla Scala non piacciono i loop nidificati ? Hai gli stessi problemi se stai provando a uscire da un singolo ciclo.
Rex Kerr,


5

Basta usare un ciclo while:

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

5

Un approccio che genera i valori su un intervallo mentre ripetiamo, fino a una condizione di rottura, invece di generare prima un intero intervallo e poi iterare su di esso, usando Iterator, (ispirato nell'uso di @RexKerr di Stream)

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

si mi piace. nessuna scusa, penso che sia più bello.
sabato

4

Ecco una versione ricorsiva della coda. Rispetto alla comprensione è un po 'enigmatico, devo dire, ma direi che è funzionale :)

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

Come puoi vedere, la funzione tr è la controparte delle comprensioni esterne e tr1 di quella interna. Sei il benvenuto se conosci un modo per ottimizzare la mia versione.


2

Vicino alla tua soluzione sarebbe questo:

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 j-iteration è fatta senza un nuovo ambito e la generazione del prodotto e la condizione sono fatte nella dichiarazione for (non una buona espressione - non ne trovo una migliore). La condizione è invertita, il che è abbastanza veloce per quella dimensione del problema - forse ottieni qualcosa con una pausa per loop più grandi.

String.reverse converte implicitamente in RichString, motivo per cui faccio 2 inversioni extra. :) Un approccio più matematico potrebbe essere più elegante.


2

Il breakablepacchetto di terze parti è una possibile alternativa

https://github.com/erikerlandson/breakable

Codice di esempio:

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

Metodo di base per interrompere il ciclo, utilizzando la classe Breaks. Dichiarando il loop come fragile.


2

Semplicemente possiamo fare in 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;
       } } } } }

produzione :

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

1

Ironia della sorte, la rottura della Scala scala.util.control.Breaksè un'eccezione:

def break(): Nothing = { throw breakException }

Il miglior consiglio è: NON usare pausa, continua e vai! IMO sono la stessa, cattiva pratica e una fonte malvagia di ogni tipo di problema (e discussioni accese) e infine "considerati dannosi". Blocco di codice strutturato, anche in questo esempio le interruzioni sono superflue. Il nostro Edsger W. Dijkstra † ha scritto:

La qualità dei programmatori è una funzione decrescente della densità di andare alle istruzioni nei programmi che producono.


1

Ho una situazione come il codice qui sotto

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

Sto usando una libreria Java e il meccanismo è che ctx.read genera un'eccezione quando non riesce a trovare nulla. Sono rimasto intrappolato nella situazione che: devo interrompere il loop quando viene generata un'eccezione, ma scala.util.control.Breaks.break utilizza l'eccezione per interrompere il loop, ed era nel blocco catch quindi è stato catturato.

Ho un brutto modo di risolverlo: esegui il loop per la prima volta e ottieni il conteggio della lunghezza reale. e usalo per il secondo loop.

fare una pausa da Scala non è così buono, quando si usano alcune librerie Java.


1

Sono nuovo di Scala, ma che ne dici di evitare di gettare eccezioni e ripetere i metodi:

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

usalo così:

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

se non vuoi rompere:

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

1

L' uso intelligente del findmetodo di raccolta farà al caso tuo.

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

Di seguito è riportato il codice per interrompere un loop in modo semplice

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

Non so quanto sia cambiato lo stile alla Scala negli ultimi 9 anni, ma ho trovato interessante l'utilizzo della maggior parte delle risposte esistenti varso la ricorsione difficile da leggere. La chiave per uscire presto è utilizzare una collezione pigra per generare i tuoi possibili candidati, quindi verificare la condizione separatamente. Per generare i prodotti:

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

Quindi per trovare il primo palindromo da quella vista senza generare ogni combinazione:

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

Per trovare il più grande palindromo (anche se la pigrizia non ti compra molto perché devi comunque controllare l'intero elenco):

palindromes.max

Il tuo codice originale sta effettivamente controllando il primo palindromo che è più grande di un prodotto successivo, che è lo stesso del controllo del primo palindromo, tranne in una strana condizione al contorno che non penso tu abbia inteso. I prodotti non stanno diminuendo rigorosamente monotonicamente. Ad esempio, 998*998è maggiore di 999*997, ma appare molto più tardi nei loop.

Ad ogni modo, il vantaggio della generazione pigra separata e del controllo delle condizioni è che lo scrivi praticamente come se stesse usando l'intero elenco, ma genera solo tutto ciò di cui hai bisogno. In un certo senso ottieni il meglio da entrambi i mondi.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.