Wie breche ich in Scala aus einer Schleife aus?

276

Wie breche ich eine Schleife aus?

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

Wie verwandle ich verschachtelte for-Schleifen in Schwanzrekursion?

Aus Scala Talk auf der FOSDEM 2009 http://www.slideshare.net/Odersky/fosdem-2009-1013261 auf der 22. Seite:

Brechen Sie und fahren Sie fort Scala hat sie nicht. Warum? Sie sind ein bisschen zwingend erforderlich; Verwenden Sie besser viele kleinere Funktionen. Fragen Sie, wie Sie mit Verschlüssen interagieren. Sie werden nicht benötigt!

Was ist die Erklärung?

TiansHUo
quelle
Ihr Vergleich benötigt ein zweites Gleichheitszeichen: if (product.toString == product.toString.reverse) oder möglicherweise einen Equals-Method-Aufruf.
Benutzer unbekannt
Ja, ich habe das verpasst, als ich es
eingetippt habe
Ich weiß, dass ich eine alte Frage wiederbelebe, aber ich würde gerne wissen, was der Zweck dieses Codes ist. Ich dachte zuerst, dass Sie versucht haben, das größtmögliche "Palindrom" -Produkt mit den angegebenen Kombinationen von iund zu finden j. Wenn dieser Code vollständig ausgeführt wird, ohne aus der Schleife 906609auszubrechen, führt 90909dies dazu, dass der Code durch frühzeitiges Ausbrechen aus der Schleife nicht "effizienter" wird, da das Ergebnis geändert wird.
Ryan H.

Antworten:

371

Sie haben drei (oder so) Optionen, um aus Schleifen auszubrechen.

Angenommen, Sie möchten Zahlen summieren, bis die Summe größer als 1000 ist. Sie versuchen es

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

außer Sie möchten aufhören, wenn (Summe> 1000).

Was ist zu tun? Es gibt mehrere Möglichkeiten.

(1a) Verwenden Sie ein Konstrukt, das eine von Ihnen getestete Bedingung enthält.

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

(Warnung - dies hängt von Details ab, wie der takeWhile-Test und der foreach während der Auswertung verschachtelt sind, und sollte in der Praxis wahrscheinlich nicht verwendet werden!).

(1b) Verwenden Sie die Schwanzrekursion anstelle einer for-Schleife, um zu nutzen, wie einfach es ist, eine neue Methode in Scala zu schreiben:

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

(1c) Verwenden Sie eine while-Schleife

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

(2) Eine Ausnahme auslösen.

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+ ist dies bereits in einer scala.util.control.BreaksSyntax vorgepackt, die Ihrer bekannten alten Pause von C / Java sehr ähnlich sieht:

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

(3) Fügen Sie den Code in eine Methode ein und verwenden Sie return.

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

Dies wird absichtlich aus mindestens drei Gründen, die mir einfallen, nicht allzu einfach gemacht. Erstens ist es in großen Codeblöcken leicht, die Anweisungen "continue" und "break" zu übersehen oder zu glauben, dass Sie mehr oder weniger ausbrechen, als Sie wirklich sind, oder zwei Schleifen brechen müssen, was Sie nicht können sowieso einfach - so hat die Standardverwendung, obwohl sie praktisch ist, ihre Probleme, und deshalb sollten Sie versuchen, Ihren Code anders zu strukturieren. Zweitens hat Scala alle Arten von Verschachtelungen, die Sie wahrscheinlich gar nicht bemerken. Wenn Sie also aus den Dingen ausbrechen könnten, wären Sie wahrscheinlich überrascht, wo der Codefluss endete (insbesondere bei Schließungen). Drittens sind die meisten "Schleifen" von Scala keine normalen Schleifen - es handelt sich um Methodenaufrufe mit einer eigenen Schleife.Schleifenartig ist es schwierig, einen konsistenten Weg zu finden, um zu wissen, was "Pause" und dergleichen tun sollten. Um konsequent zu sein, ist es klüger, überhaupt keine "Pause" zu machen.

Hinweis : Es gibt funktionale Äquivalente von all diesen, bei denen Sie den Wert von zurückgeben, sumanstatt ihn an Ort und Stelle zu mutieren. Dies sind eher idiomatische Scala. Die Logik bleibt jedoch gleich. ( returnwird return xusw.).

Rex Kerr
quelle
9
Zu Ausnahmen, obwohl strikt zutreffend, dass Sie eine Ausnahme auslösen können, ist dies wohl ein Missbrauch des Ausnahmemechanismus (siehe Effektives Java). Ausnahmen sind wirklich Situationen, die wirklich unerwartet sind und / oder ein drastisches Entkommen aus dem Code erfordern, dh Fehler irgendeiner Art. Abgesehen davon waren sie sicherlich ziemlich langsam (nicht sicher über die aktuelle Situation), da es für JVMs wenig Grund gibt, sie zu optimieren.
Jonathan
28
@ Jonathan - Ausnahmen sind nur langsam, wenn Sie eine Stapelverfolgung berechnen müssen - beachten Sie, wie ich eine statische Ausnahme zum Auslösen erstellt habe, anstatt eine im laufenden Betrieb zu generieren! Und sie sind ein absolut gültiges Kontrollkonstrukt. Sie werden an mehreren Stellen in der Scala-Bibliothek verwendet, da sie wirklich die einzige Möglichkeit sind, mit mehreren Methoden zurückzukehren (was Sie manchmal tun müssen, wenn Sie einen Stapel Verschlüsse haben).
Rex Kerr
18
@ Rex Kerr, Sie weisen auf Schwachstellen des Break-Konstrukts hin (ich stimme ihnen nicht zu), aber dann schlagen Sie vor, Ausnahmen für den normalen Workflow zu verwenden! Das Verlassen einer Schleife ist kein Ausnahmefall, es ist Teil des Algorithmus, es ist nicht der Fall, in eine nicht vorhandene Datei zu schreiben (zum Beispiel). Kurz gesagt, "Heilung" ist schlimmer als die "Krankheit" selbst. Und wenn ich darüber nachdenke, eine echte Ausnahme in breakableAbschnitt zu werfen ... und all diese Reifen, nur um das Böse zu vermeiden break, hmm ;-) Man muss zugeben, das Leben ist ironisch.
Greenoldman
17
@macias - Entschuldigung, mein Fehler. Die JVM verwendet Throwables für den Kontrollfluss. Besser? Nur weil sie normalerweise zur Unterstützung der Ausnahmebehandlung verwendet werden, bedeutet dies nicht, dass sie nur zur Ausnahmebehandlung verwendet werden können. Das Zurückkehren zu einem definierten Ort innerhalb eines Abschlusses ist wie das Auslösen einer Ausnahme in Bezug auf den Kontrollfluss. Kein Wunder also, dass dies der verwendete Mechanismus ist.
Rex Kerr
14
@ RexKerr Nun, für das, was es wert ist, hast du mich überzeugt. Normalerweise würde ich gegen Ausnahmen für den normalen Programmablauf schimpfen, aber die beiden Hauptgründe gelten hier nicht. Dies sind: (1) sie sind langsam [nicht, wenn sie auf diese Weise verwendet werden], und (2) sie schlagen jemandem, der Ihren Code liest, außergewöhnliches Verhalten vor [nicht, wenn Ihre Bibliothek Sie sie aufrufen lässt break], wenn es wie a aussieht breakund es funktioniert wie ein break, soweit es mich betrifft, ist es ein break.
Tim Goodman
66

Dies hat sich in Scala 2.8 geändert, das über einen Mechanismus zur Verwendung von Pausen verfügt. Sie können jetzt Folgendes tun:

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
quelle
3
Verwendet dies Ausnahmen unter der Haube?
Mike
Hierbei wird Scala als prozedurale Sprache verwendet, wobei die Vorteile der funktionalen Programmierung (dh die Schwanzrekursion) ignoriert werden. Nicht hübsch.
Galder Zamarreño
32
Mike: Ja, Scala wirft eine Ausnahme, um aus der Schleife auszubrechen. Galder: Dies beantwortet die gestellte Frage "Wie kann ich in Scala aus einer Schleife ausbrechen?". Ob es "hübsch" ist oder nicht, ist nicht relevant.
Hohonuuli
2
@hohonuuli, also ist es im Try-Catch-Block, es wird nicht brechen, oder?
Greenoldman
2
@Galder Zamarreño Warum ist die Schwanzrekursion in diesem Fall von Vorteil? Ist es nicht einfach eine Optimierung (wer die Anwendung für den Neuankömmling versteckt und verwirrend für den Erfahrenen angewendet hat). Gibt es in diesem Beispiel einen Vorteil für die Schwanzrekursion?
Benutzer48956
32

Es ist niemals eine gute Idee, aus einer for-Schleife auszubrechen. Wenn Sie eine for-Schleife verwenden, bedeutet dies, dass Sie wissen, wie oft Sie iterieren möchten. Verwenden Sie eine while-Schleife mit 2 Bedingungen.

beispielsweise

var done = false
while (i <= length && !done) {
  if (sum > 1000) {
     done = true
  }
}
Keith Blanchard
quelle
2
Dies ist meiner Meinung nach der richtige Weg, um in Scala aus Schleifen auszubrechen. Stimmt etwas mit dieser Antwort nicht? (in Anbetracht der geringen Anzahl von Upvotes).
Jus12
1
in der Tat einfach und besser lesbar. Sogar das Breakable-Break-Ding ist korrekt, es sieht hässlich aus und hat Probleme mit dem inneren Try-Catch. Obwohl Ihre Lösung nicht mit einem foreach funktioniert, werde ich Sie abstimmen und die Einfachheit würdigen.
Yerlilbilgin
13

Um Rex Kerr hinzuzufügen, antworten Sie auf eine andere Weise:

  • (1c) Sie können auch einen Schutz in Ihrer Schleife verwenden:

     var sum = 0
     for (i <- 0 to 1000 ; if sum<1000) sum += i
Patrick
quelle
30
Ich habe dies nicht als Option aufgenommen, da es die Schleife nicht wirklich durchbricht - es durchläuft alles, aber die if-Anweisung schlägt bei jeder Iteration fehl, nachdem die Summe hoch genug ist, sodass nur eine if-Anweisung ausgeführt wird jedes Mal Arbeit wert. Je nachdem, wie Sie die Schleife geschrieben haben, kann dies leider eine Menge Arbeit bedeuten.
Rex Kerr
@ RexKerr: Würde der Compiler es nicht trotzdem optimieren? Wird es nicht optimiert, wenn nicht während des ersten Laufs, dann während der JIT.
Maciej Piechotka
5
@MaciejPiechotka - Der JIT-Compiler enthält im Allgemeinen keine ausreichend ausgefeilte Logik, um zu erkennen, dass eine if-Anweisung für eine sich ändernde Variable immer (in dieser speziellen Situation) false zurückgibt und daher weggelassen werden kann.
Rex Kerr
6

Da es breakin Scala noch keine gibt, können Sie versuchen, dieses Problem mit einer returnAnweisung zu lösen . Daher müssen Sie Ihre innere Schleife in eine Funktion einfügen, da sonst die Rückgabe die gesamte Schleife überspringen würde.

Scala 2.8 enthält jedoch eine Möglichkeit zum Brechen

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

Ham Vocke
quelle
Entschuldigung, aber ich wollte nur die innere Schleife ausbrechen. Sie implizieren nicht, dass ich es in eine Funktion setzen sollte?
TiansHUo
Entschuldigung, hätte das klarstellen sollen. Sicher bedeutet die Verwendung einer Rückgabe, dass Sie die Schleife in eine Funktion einkapseln müssen. Ich habe meine Antwort bearbeitet.
Ham Vocke
1
Das ist überhaupt nicht schön. Es scheint, dass Scala keine verschachtelten Schleifen mag.
TiansHUo
Es scheint keinen anderen Weg zu geben. Vielleicht möchten Sie einen Blick darauf werfen
Ham Vocke
4
@TiansHUo: Warum sagst du, dass Scala keine verschachtelten Schleifen mag ? Sie haben die gleichen Probleme, wenn Sie versuchen, aus einer einzelnen Schleife auszubrechen .
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;
   }
}

Verwenden Sie das Break-Modul http://www.tutorialspoint.com/scala/scala_break_statement.htm

user1836270
quelle
5

Verwenden Sie einfach eine while-Schleife:

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

Ein Ansatz, der die Werte über einen Bereich generiert, während wir bis zu einer Unterbrechungsbedingung iterieren, anstatt zuerst einen ganzen Bereich zu generieren und dann mit Iterator(iteriert in @RexKerr use of Stream) darüber zu iterieren.

var sum = 0
for ( i <- Iterator.from(1).takeWhile( _ => sum < 1000) ) sum += i
Ulme
quelle
ja ich mag es. Keine zerbrechliche Entschuldigung, ich denke es sieht besser aus.
Ses
4

Hier ist eine rekursive Schwanzversion. Im Vergleich zu den Vorverständnissen ist es zwar ein bisschen kryptisch, aber ich würde sagen, es ist funktional :)

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

Wie Sie sehen können, ist die tr-Funktion das Gegenstück zum äußeren Verständnis und tr1 zum inneren. Sie sind herzlich willkommen, wenn Sie eine Möglichkeit kennen, meine Version zu optimieren.

Fresskoma
quelle
2

In der Nähe Ihrer Lösung wäre dies:

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)

Die j-Iteration wird ohne neuen Bereich durchgeführt, und die Produktgenerierung sowie die Bedingung werden in der for-Anweisung ausgeführt (kein guter Ausdruck - ich finde keinen besseren). Der Zustand ist umgekehrt, was für diese Problemgröße ziemlich schnell ist - vielleicht gewinnen Sie etwas mit einer Pause für größere Schleifen.

String.reverse konvertiert implizit in RichString, weshalb ich zwei zusätzliche Umkehrungen mache. :) Ein mathematischerer Ansatz könnte eleganter sein.

Benutzer unbekannt
quelle
2

Das Drittanbieterpaket breakableist eine mögliche Alternative

https://github.com/erikerlandson/breakable

Beispielcode:

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

Grundlegende Methode zum Unterbrechen der Schleife mithilfe der Breaks-Klasse. Indem Sie die Schleife als zerbrechlich deklarieren.

Parvathirajan Natarajan
quelle
2

Einfach können wir in scala machen

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

Ausgabe :

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

Ironischerweise ist der Einbruch der Scala scala.util.control.Breakseine Ausnahme:

def break(): Nothing = { throw breakException }

Der beste Rat ist: NICHT Pause verwenden, fortfahren und loslegen! IMO sind sie die gleiche, schlechte Praxis und eine böse Quelle aller Arten von Problemen (und heißen Diskussionen) und schließlich "als schädlich angesehen". Codeblock strukturiert, auch in diesem Beispiel sind Unterbrechungen überflüssig. Unser Edsger W. Dijkstra † schrieb:

Die Qualität von Programmierern ist eine abnehmende Funktion der Dichte von Go-to-Anweisungen in den von ihnen produzierten Programmen.

Epikurist
quelle
1

Ich habe eine Situation wie den folgenden Code

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

Ich benutze eine Java-Bibliothek und der Mechanismus ist, dass ctx.read eine Ausnahme auslöst, wenn es nichts finden kann. Ich war in der Situation gefangen, dass: Ich muss die Schleife brechen, wenn eine Ausnahme ausgelöst wurde, aber scala.util.control.Breaks.break verwendet Exception, um die Schleife zu unterbrechen, und sie befand sich im catch-Block, sodass sie abgefangen wurde.

Ich habe einen hässlichen Weg gefunden, dies zu lösen: Mach die Schleife zum ersten Mal und zähle die tatsächliche Länge. und benutze es für die zweite Schleife.

Eine Pause von Scala zu machen ist nicht so gut, wenn Sie einige Java-Bibliotheken verwenden.

Lucas Liu
quelle
1

Ich bin neu in Scala, aber wie wäre es damit, Ausnahmen zu vermeiden und Methoden zu wiederholen:

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

benutze es so:

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

wenn du nicht brechen willst:

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

Die geschickte Anwendung der findSammelmethode erledigt den Trick für Sie.

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
quelle
1

Unten finden Sie Code zum einfachen Unterbrechen einer Schleife

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
quelle
1

Ich weiß nicht, wie sehr sich der Scala-Stil in den letzten 9 Jahren geändert hat, aber ich fand es interessant, dass die meisten vorhandenen Antworten eine varsRekursion verwenden oder schwer zu lesen sind. Der Schlüssel zum vorzeitigen Beenden besteht darin, eine faule Sammlung zu verwenden, um mögliche Kandidaten zu generieren, und dann die Bedingung separat zu prüfen. So generieren Sie die Produkte:

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

Um dann das erste Palindrom aus dieser Ansicht zu finden, ohne jede Kombination zu generieren:

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

So finden Sie das größte Palindrom (obwohl Ihnen die Faulheit nicht viel kostet, weil Sie sowieso die gesamte Liste überprüfen müssen):

palindromes.max

Ihr ursprünglicher Code sucht tatsächlich nach dem ersten Palindrom, das größer als ein nachfolgendes Produkt ist. Dies entspricht der Suche nach dem ersten Palindrom, außer in einer seltsamen Randbedingung, die Sie meiner Meinung nach nicht beabsichtigt haben. Die Produkte nehmen nicht streng monoton ab. Ist zum Beispiel 998*998größer als 999*997, erscheint aber viel später in den Schleifen.

Der Vorteil der getrennten verzögerten Generierungs- und Bedingungsprüfung besteht darin, dass Sie sie so schreiben, als würde sie die gesamte Liste verwenden, aber nur so viel generieren, wie Sie benötigen. Sie bekommen sozusagen das Beste aus beiden Welten.

Karl Bielefeldt
quelle