Comment sortir d'une boucle dans Scala?


276

Comment sortir une boucle?

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

Comment transformer des boucles imbriquées en récursivité de queue?

Extrait de Scala Talk au FOSDEM 2009 http://www.slideshare.net/Odersky/fosdem-2009-1013261 sur la 22e page:

Briser et continuer Scala n'en a pas. Pourquoi? Ils sont un peu impératifs; mieux utiliser de nombreuses fonctions plus petites Problème d'interaction avec les fermetures. Ils ne sont pas nécessaires!

Quelle est l'explication?


Votre comparaison nécessite un second signe égal: if (product.toString == product.toString.reverse) ou peut-être un appel de méthode égale.
utilisateur inconnu

ouais, j'ai raté celui-là quand je l'ai tapé
TiansHUo

Je sais que je ressuscite une vieille question mais j'aimerais savoir quel est le but de ce code? J'ai d'abord pensé que vous essayiez de trouver le plus grand produit "palindrome" possible avec les combinaisons données de iet j. Si ce code s'exécute jusqu'à la fin sans sortir de la boucle, le résultat est, 906609mais en sortant tôt de la boucle, le résultat est 90909si la rupture de la boucle ne rend pas le code "plus efficace" car il modifie le résultat.
Ryan H.

Réponses:


371

Vous avez trois (ou plus) options pour sortir des boucles.

Supposons que vous souhaitiez additionner des nombres jusqu'à ce que le total soit supérieur à 1000. Vous essayez

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

sauf que vous voulez arrêter quand (somme> 1000).

Que faire? Il existe plusieurs options.

(1a) Utilisez une construction qui inclut un conditionnel que vous testez.

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

(avertissement - cela dépend des détails de la façon dont le test takeWhile et le foreach sont entrelacés pendant l'évaluation, et ne devraient probablement pas être utilisés dans la pratique!).

(1b) Utilisez la récursion de queue au lieu d'une boucle for, en profitant de la facilité d'écriture d'une nouvelle méthode dans Scala:

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

(1c) Revenez à l'utilisation d'une boucle while

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

(2) Jetez une exception.

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

(2a) Dans Scala 2.8+, ceci est déjà pré-packagé en scala.util.control.Breaksutilisant une syntaxe qui ressemble beaucoup à votre ancienne rupture familière de C / Java:

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

(3) Mettez le code dans une méthode et utilisez return.

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

C'est intentionnellement rendu pas trop facile pour au moins trois raisons auxquelles je peux penser. Tout d'abord, dans les grands blocs de code, il est facile d'ignorer les instructions "continue" et "break", ou de penser que vous vous échappez de plus ou de moins que vous ne l'êtes réellement, ou d'avoir besoin de casser deux boucles que vous ne pouvez pas faire facilement de toute façon - donc l'utilisation standard, bien que pratique, a ses problèmes, et donc vous devriez essayer de structurer votre code d'une manière différente. Deuxièmement, Scala a toutes sortes d'imbrications que vous ne remarquerez probablement même pas, donc si vous pouviez vous échapper, vous seriez probablement surpris de la fin du flux de code (en particulier avec les fermetures). Troisièmement, la plupart des "boucles" de Scala ne sont pas en fait des boucles normales - ce sont des appels de méthode qui ont leur propre boucle,Looplike, il est difficile de trouver un moyen cohérent de savoir ce que la «rupture» et autres devraient faire. Donc, pour être cohérent, la chose la plus sage à faire est de ne pas avoir du tout de "pause".

Remarque : il existe des équivalents fonctionnels de tous ces éléments dans lesquels vous retournez la valeur de sumplutôt que de la muter en place. Ce sont des Scala plus idiomatiques. Cependant, la logique reste la même. ( returndevient return x, etc.).


9
Concernant les exceptions, bien qu'il soit strictement vrai que vous pouvez lever une exception, il s'agit sans doute d'un abus du mécanisme d'exception (voir Java efficace). Les exceptions sont vraiment pour indiquer des situations qui sont vraiment inattendues et / ou nécessitent une fuite drastique du code, c'est-à-dire des erreurs en quelque sorte. En dehors de cela, ils étaient certainement assez lents (pas sûr de la situation actuelle) car il n'y a pas de raison pour que les JVM les optimisent.
Jonathan

28
@Jonathan - Les exceptions ne sont lentes que si vous devez calculer une trace de pile - remarquez comment j'ai créé une exception statique à lancer au lieu d'en générer une à la volée! Et ils sont une construction de contrôle parfaitement valide; ils sont utilisés à plusieurs endroits dans la bibliothèque Scala, car ils sont vraiment le seul moyen de revenir via plusieurs méthodes (ce que vous devez parfois faire si vous avez une pile de fermetures).
Rex Kerr

18
@Rex Kerr, vous signalez les faiblesses de la construction de rupture (je ne suis pas d'accord avec elles), mais vous proposez ensuite d'utiliser des exceptions pour le flux de travail normal! La sortie d'une boucle n'est pas un cas exceptionnel, elle fait partie de l'algorithme, ce n'est pas le cas d'écrire dans un fichier inexistant (par exemple). Bref, la "guérison" suggérée est pire que la "maladie" elle-même. Et quand je pense à jeter une vraie exception dans la breakablesection ... et tous ces cerceaux juste pour éviter le mal break, hmm ;-) Vous devez admettre que la vie est ironique.
greenoldman

17
@macias - Désolé, mon erreur. La JVM utilise Throwables pour contrôler le flux. Mieux? Ce n'est pas parce qu'ils sont généralement utilisés pour prendre en charge la gestion des exceptions qu'ils ne peuvent être utilisés que pour la gestion des exceptions. Revenir à un emplacement défini à partir d'une fermeture, c'est comme lancer une exception en termes de flux de contrôle. Pas étonnant, alors, que ce soit le mécanisme qui soit utilisé.
Rex Kerr

14
@RexKerr Eh bien, pour ce que ça vaut vous m'avez convaincu. Normalement, je serais du genre à dénoncer les exceptions pour un flux de programme normal, mais les deux principales raisons ne s'appliquent pas ici. Ce sont: (1) ils sont lents [pas lorsqu'ils sont utilisés de cette façon], et (2) ils suggèrent un comportement exceptionnel à quelqu'un qui lit votre code [pas si votre bibliothèque vous permet de les appeler break] Si cela ressemble à un breaket qu'il fonctionne comme un break, en ce qui me concerne c'est un break.
Tim Goodman,

66

Cela a changé dans Scala 2.8 qui a un mécanisme pour utiliser les pauses. Vous pouvez maintenant effectuer les opérations suivantes:

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
Est-ce que cela utilise des exceptions sous le capot?
Mike

Ceci utilise Scala comme un langage procédural ignorant les avantages de la programmation fonctionnelle (c'est-à-dire la récursivité de la queue). Pas beau.
Galder Zamarreño

32
Mike: Oui, Scala lève une exception pour sortir de la boucle. Galder: Cela répond à la question publiée "Comment puis-je sortir d'une boucle dans Scala?". Que ce soit «joli» ou non n'est pas pertinent.
hohonuuli

2
@hohonuuli, donc c'est dans le bloc try-catch ça ne cassera pas, non?
greenoldman

2
@Galder Zamarreño Pourquoi la récursivité de la queue est-elle un avantage dans ce cas? N'est-ce pas simplement une optimisation (dont l'application est cachée pour le nouveau venu et appliquée de manière confuse pour l'expérimenté). Y a-t-il un avantage pour la récursivité de queue dans cet exemple?
user48956

33

Ce n'est jamais une bonne idée de sortir d'une boucle for. Si vous utilisez une boucle for, cela signifie que vous savez combien de fois vous souhaitez répéter. Utilisez une boucle while avec 2 conditions.

par exemple

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

2
C'est ce que je pense être la bonne façon de sortir des boucles de Scala. Y a-t-il quelque chose de mal avec cette réponse? (compte tenu du faible nombre de votes positifs).
Jus12

1
en effet simple et plus lisible. même le truc cassable - cassé est correct, il a l'air moche et a des problèmes avec le try-catch intérieur. Bien que votre solution ne fonctionne pas avec un foreach, je voterai pour vous, honorant la simplicité.
yerlilbilgin

13

Pour ajouter Rex Kerr, répondez d'une autre manière:

  • (1c) Vous pouvez également utiliser un garde dans votre boucle:

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

30
Je n'ai pas inclus cela comme option car il ne rompt pas réellement la boucle - il parcourt tout cela, mais l'instruction if échoue à chaque itération après que la somme est suffisamment élevée, donc elle ne fait qu'une seule instruction if valeur de travail à chaque fois. Malheureusement, selon la façon dont vous avez écrit la boucle, cela pourrait demander beaucoup de travail.
Rex Kerr

@RexKerr: Le compilateur ne l'optimiserait-il pas de toute façon? Ne sera-t-il pas optimisé si ce n'est lors de la première exécution puis pendant JIT.
Maciej Piechotka

5
@MaciejPiechotka - Le compilateur JIT ne contient généralement pas de logique suffisamment sophistiquée pour reconnaître qu'une instruction if sur une variable changeante retournera toujours (dans cette situation particulière particulière) false et peut donc être omise.
Rex Kerr

6

Puisqu'il n'y en a pas encore breakdans Scala, vous pouvez essayer de résoudre ce problème en utilisant une returndéclaration. Par conséquent, vous devez mettre votre boucle intérieure dans une fonction, sinon le retour ignorerait la boucle entière.

Scala 2.8 comprend cependant un moyen de briser

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


désolé, mais je voulais seulement briser la boucle intérieure. Vous n'impliquez pas que je devrais le mettre dans une fonction?
TiansHUo

Désolé, j'aurais dû clarifier cela. Bien sûr, l'utilisation d'un retour signifie que vous devez encapsuler la boucle dans une fonction. J'ai édité ma réponse.
Ham Vocke

1
Ce n'est pas agréable du tout. Il semble que Scala n'aime pas les boucles imbriquées.
TiansHUo

Il ne semble pas y avoir de manière différente. Vous voudrez peut-être y jeter un œil: scala-lang.org/node/257
Ham Vocke

4
@TiansHUo: Pourquoi dites-vous que Scala n'aime pas les boucles imbriquées ? Vous rencontrez les mêmes problèmes si vous essayez de sortir d'une seule boucle.
Rex Kerr


5

Utilisez simplement une boucle while:

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

5

Une approche qui génère les valeurs sur une plage au fur et à mesure que nous itérons, jusqu'à une condition de rupture, au lieu de générer d'abord une plage entière, puis d'itérer dessus, en utilisant Iterator, (inspiré de l'utilisation @RexKerr de Stream)

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

Oui j'aime ça. pas d'excuse cassable, je pense que c'est plus joli.
ses

4

Voici une version récursive de queue. Comparé aux for-comprehensions, c'est un peu cryptique, certes, mais je dirais que c'est fonctionnel :)

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

Comme vous pouvez le voir, la fonction tr est la contrepartie des for-comprehensions externes et tr1 de la for-comprehensions interne. Vous êtes les bienvenus si vous connaissez un moyen d'optimiser ma version.


2

La solution la plus proche serait la suivante:

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-itération est faite sans nouvelle portée, et la génération du produit ainsi que la condition sont effectuées dans l'instruction for (pas une bonne expression - je n'en trouve pas de meilleure). La condition est inversée, ce qui est assez rapide pour cette taille de problème - peut-être que vous gagnez quelque chose avec une pause pour les boucles plus grandes.

String.reverse se convertit implicitement en RichString, c'est pourquoi je fais 2 revers supplémentaires. :) Une approche plus mathématique pourrait être plus élégante.


2

Le breakablepackage tiers est une alternative possible

https://github.com/erikerlandson/breakable

Exemple de code:

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

Méthode de base pour rompre la boucle, en utilisant la classe Breaks. En déclarant la boucle cassable.


2

Tout simplement, nous pouvons faire en scala est

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

production :

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

1

Ironiquement, l'effraction de Scala scala.util.control.Breaksest une exception:

def break(): Nothing = { throw breakException }

Le meilleur conseil est: NE PAS utiliser break, continue et goto! OMI, ce sont les mêmes, les mauvaises pratiques et une source perverse de toutes sortes de problèmes (et de discussions brûlantes) et finalement "considérées comme nuisibles". Le bloc de code est structuré, également dans cet exemple les ruptures sont superflues. Notre Edsger W. Dijkstra † a écrit:

La qualité des programmeurs est une fonction décroissante de la densité des instructions go to dans les programmes qu'ils produisent.


1

J'ai une situation comme le code ci-dessous

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

J'utilise une bibliothèque java et le mécanisme est que ctx.read lève une exception quand il ne trouve rien. J'étais coincé dans la situation suivante: je dois rompre la boucle lorsqu'une exception a été levée, mais scala.util.control.Breaks.break utilise Exception pour rompre la boucle, et il était dans le bloc catch donc il a été intercepté.

J'ai eu laide façon de résoudre ce problème: faites la boucle pour la première fois et obtenez le compte de la longueur réelle. et l'utiliser pour la deuxième boucle.

retirer une pause de Scala n'est pas si bon, quand vous utilisez des bibliothèques java.


1

Je suis nouveau sur Scala, mais que diriez-vous pour éviter de lever des exceptions et de répéter les méthodes:

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

utilisez-le comme ceci:

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

si vous ne voulez pas casser:

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

1

Une utilisation intelligente de la findméthode de collecte fera l'affaire pour vous.

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

Ci-dessous, le code pour rompre une boucle d'une manière 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"

        }
      }
    }
  }
}

1

Je ne sais pas à quel point le style Scala a changé au cours des 9 dernières années, mais j'ai trouvé intéressant que la plupart des réponses existantes utilisent vars, ou difficile à lire la récursivité. La clé pour sortir tôt est d'utiliser une collection paresseuse pour générer vos candidats potentiels, puis vérifiez la condition séparément. Pour générer les produits:

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

Ensuite, pour trouver le premier palindrome à partir de cette vue sans générer toutes les combinaisons:

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

Pour trouver le plus grand palindrome (bien que la paresse ne vous achète pas beaucoup car vous devez quand même consulter la liste entière):

palindromes.max

Votre code d'origine vérifie en fait le premier palindrome qui est plus grand qu'un produit suivant, ce qui revient à vérifier le premier palindrome, sauf dans une condition aux limites étrange que je ne pense pas que vous vouliez. Les produits ne diminuent pas strictement de façon monotone. Par exemple, 998*998est supérieur à 999*997, mais apparaît beaucoup plus tard dans les boucles.

Quoi qu'il en soit, l'avantage de la génération paresseuse séparée et de la vérification de l'état est que vous l'écrivez à peu près comme s'il utilisait la liste entière, mais il ne génère que ce dont vous avez besoin. Vous obtenez en quelque sorte le meilleur des deux mondes.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.