Como faço para sair de um loop no Scala?

276

Como faço para quebrar um loop?

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

Como transformar aninhados para loops em recursão de cauda?

Do Scala Talk no FOSDEM 2009 http://www.slideshare.net/Odersky/fosdem-2009-1013261 na 22ª página:

Quebre e continue Scala não os possui. Por quê? Eles são um pouco imperativos; melhor usar muitas funções menores Emita como interagir com fechamentos. Eles não são necessários!

Qual a explicação?

TiansHUo
fonte
Sua comparação precisa de um segundo sinal de igual: if (product.toString == product.toString.reverse) ou talvez uma chamada igual a Method.
usuário desconhecido
Sim, eu perdi aquele quando eu estava digitando-o
TiansHUo
Sei que estou ressuscitando uma pergunta antiga, mas gostaria de saber qual é o objetivo deste código? Primeiro, pensei que você estava tentando encontrar o maior produto "palíndromo" possível com as combinações fornecidas de ie j. Se esse código for concluído sem interromper o loop, o resultado é que, 906609ao sair do loop mais cedo, o resultado será 90909que o loop não estará tornando o código "mais eficiente", pois está alterando o resultado.
21718 Ryan H.

Respostas:

371

Você tem três (mais ou menos) opções para interromper os loops.

Suponha que você queira somar números até que o total seja maior que 1000. Você tenta

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

exceto que você deseja parar quando (soma> 1000).

O que fazer? Existem várias opções.

(1a) Use alguma construção que inclua uma condição que você teste.

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

(aviso - isso depende dos detalhes de como o teste takeWhile e o foreach são intercalados durante a avaliação e provavelmente não devem ser usados ​​na prática!).

(1b) Use recursão de cauda em vez de um loop for, aproveitando a facilidade de escrever um novo método no Scala:

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

(1c) Volte a usar um loop while

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

(2) Lance uma exceção.

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

(2a) No Scala 2.8+, isso já está pré-empacotado no scala.util.control.Breaksuso de sintaxe que se parece muito com a sua antiga quebra familiar do C / Java:

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

(3) Coloque o código em um método e use return.

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

Isso é intencionalmente tornado não muito fácil por pelo menos três razões em que consigo pensar. Primeiro, em grandes blocos de código, é fácil ignorar as instruções "continue" e "break", ou pensar que você está quebrando mais ou menos do que realmente é, ou precisar interromper dois loops que você não pode executar de qualquer maneira facilmente - portanto, o uso padrão, embora útil, tem seus problemas e, portanto, você deve tentar estruturar seu código de maneira diferente. Segundo, Scala tem todos os tipos de aninhamentos que você provavelmente nem percebe; portanto, se você pudesse se libertar, provavelmente ficaria surpreso com a localização do fluxo do código (principalmente nos fechamentos). Terceiro, a maioria dos "loops" de Scala não são realmente loops normais - são chamadas de método que têm seu próprio loop,de maneira irregular, é difícil encontrar uma maneira consistente de saber o que "quebra" e coisas semelhantes devem fazer. Portanto, para ser consistente, a coisa mais sábia a fazer é não ter uma "pausa".

Nota : Existem equivalentes funcionais de todos eles em que você retorna o valor, em sumvez de modificá-lo no lugar. Estes são Scala mais idiomáticos. No entanto, a lógica permanece a mesma. ( returntorna-se return x, etc.).

Rex Kerr
fonte
9
Reexceções, embora seja estritamente verdade que você pode lançar uma exceção, é indiscutivelmente um abuso do mecanismo de exceção (consulte Java efetivo). As exceções são realmente para indicar situações que são realmente inesperadas e / ou exigem uma fuga drástica do código, isto é, erros de algum tipo. Além disso, eles certamente costumavam ser muito lentos (não tenho certeza da situação atual) porque há poucas razões para as JVMs otimizá-las.
Jonathan
28
@ Jonathan - Exceções só são lentas se você precisar calcular um rastreamento de pilha - observe como eu criei uma exceção estática para lançar em vez de gerar uma em tempo real! E eles são uma construção de controle perfeitamente válida; eles são usados ​​em vários lugares da biblioteca Scala, já que são realmente a única maneira de você retornar através de vários métodos (que, se você tem uma pilha de fechamentos, é algo que às vezes precisa fazer).
Rex Kerr #
18
@Rex Kerr, você está apontando pontos fracos da construção de interrupção (não concordo com eles), mas sugere sugerir o uso de exceções no fluxo de trabalho normal! Sair de um loop não é um caso excepcional, faz parte do algoritmo, não é o caso de gravar em um arquivo inexistente (por exemplo). Então, em suma, a "cura" sugerida é pior que a "doença" em si. E quando eu considero lançar uma exceção real na breakableseção ... e todos esses argumentos apenas para evitar o mal break, hmm ;-) Você tem que admitir, a vida é irônica.
greenoldman
17
@macias - Desculpe, meu erro. A JVM está usando Throwables para controlar o fluxo. Melhor? Só porque eles geralmente são usados ​​para oferecer suporte ao tratamento de exceções não significa que eles só podem ser usados ​​para tratamento de exceções. Retornar para um local definido de dentro de um fechamento é como lançar uma exceção em termos de fluxo de controle. Não surpreende, portanto, que este seja o mecanismo usado.
Rex Kerr
14
@RexKerr Bem, pelo que vale a pena, você me convenceu. Normalmente eu seria contra as exceções para o fluxo normal do programa, mas os dois principais motivos não se aplicam aqui. São eles: (1) são lentos [não usados ​​dessa maneira] e (2) sugerem um comportamento excepcional para quem lê seu código [não se a sua biblioteca permitir que você os chame break] Se parecer com um breake executa como um break, tanto quanto eu estou preocupado é um break.
Tim Goodman
66

Isso mudou no Scala 2.8, que possui um mecanismo para usar quebras. Agora você pode fazer o seguinte:

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
fonte
3
Isso usa exceções sob o capô?
Mike
Isso está usando o Scala como uma linguagem processual, ignorando as vantagens da programação funcional (isto é, recursão da cauda). Feio.
Galder Zamarreño
32
Mike: Sim, Scala está lançando uma exceção para sair do circuito. Galder: Isso responde à pergunta postada "Como faço para sair de um loop no Scala?". Se é "bonito" ou não, não é relevante.
hohonuuli
2
@hohonuuli, então está no bloco try-catch, não vai quebrar, certo?
greenoldman
2
@Galder Zamarreño Por que a recursão da cauda é uma vantagem neste caso? Não é simplesmente uma otimização (quem está oculto para o recém-chegado e confusamente aplicado para o experiente). Existe algum benefício para a recursão da cauda neste exemplo?
user48956
32

Nunca é uma boa ideia sair do loop for. Se você estiver usando um loop for, significa que você sabe quantas vezes deseja iterar. Use um loop while com 2 condições.

por exemplo

var done = false
while (i <= length && !done) {
  if (sum > 1000) {
     done = true
  }
}
Keith Blanchard
fonte
2
É isso que considero o caminho certo para romper os laços em Scala. Há algo de errado com esta resposta? (considerando o baixo número de votos positivos).
precisa saber é o seguinte
1
realmente simples e mais legível. até o breakable - break thingy está correto, parece feio e tem problemas com o try-catch interno. Embora sua solução não funcione com um foreach, eu votarei em você, honrando a simplicidade.
25416 #
13

Para adicionar a resposta de Rex Kerr de outra maneira:

  • (1c) Você também pode usar uma proteção em seu loop:

     var sum = 0
     for (i <- 0 to 1000 ; if sum<1000) sum += i
    
Patrick
fonte
30
Eu não incluí isso como uma opção, porque na verdade não quebra o loop - ele percorre tudo isso, mas a instrução if falha em todas as iterações após a soma ser alta o suficiente, portanto, apenas uma instrução if valor do trabalho de cada vez. Infelizmente, dependendo de como você escreveu o loop, isso pode dar muito trabalho.
Rex Kerr
@RexKerr: O compilador não o otimizaria de qualquer maneira? Não será otimizado se não for na primeira execução e depois no JIT.
Maciej Piechotka
5
@MaciejPiechotka - Geralmente, o compilador JIT não contém lógica suficientemente sofisticada para reconhecer que uma instrução if em uma variável em mudança sempre (nesta situação especial específica) retornará falso e, portanto, pode ser omitida.
Rex Kerr
6

Como ainda não existe breakno Scala, você pode tentar resolver esse problema usando uma returndeclaração. Portanto, você precisa colocar seu loop interno em uma função; caso contrário, o retorno pulará o loop inteiro.

Scala 2.8, no entanto, inclui uma maneira de quebrar

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

Ham Vocke
fonte
desculpe, mas eu só queria quebrar o loop interno. Você não está sugerindo que eu deveria colocá-lo em uma função?
TiansHUo
Desculpe, deveria ter esclarecido isso. Certamente, usar um retorno significa que você precisa encapsular o loop em uma função. Eu editei minha resposta.
Ham Vocke
1
Isso não é nada legal. Parece que Scala não gosta de loops aninhados.
TiansHUo
Não parece haver uma maneira diferente. Você pode dar uma olhada nisso: scala-lang.org/node/257
Ham Vocke
4
@TiansHUo: Por que você diz que Scala não gosta de loops aninhados ? Você tem os mesmos problemas se estiver tentando interromper um único loop.
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 o módulo Break http://www.tutorialspoint.com/scala/scala_break_statement.htm

user1836270
fonte
5

Basta usar um loop while:

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

Uma abordagem que gera os valores em um intervalo à medida que iteramos, até uma condição de interrupção, em vez de gerar primeiro um intervalo inteiro e depois iterá-lo, usando Iterator, (inspirado no uso do @RexKerr Stream)

var sum = 0
for ( i <- Iterator.from(1).takeWhile( _ => sum < 1000) ) sum += i
olmo
fonte
sim eu gosto. sem desculpa quebrável, acho que parece melhor.
ses
4

Aqui está uma versão recursiva da cauda. Comparado com as compreensões, é um pouco enigmático, é certo, mas eu diria que é 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 você pode ver, a função tr é a contrapartida das compreensões externas e tr1 da interna. Você é bem-vindo se souber uma maneira de otimizar minha versão.

fresskoma
fonte
2

Perto da sua solução seria esta:

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)

A iteração j é feita sem um novo escopo, e a geração do produto e a condição são feitas na declaração for (não é uma boa expressão - não encontro uma melhor). A condição é invertida, o que é bastante rápido para o tamanho do problema - talvez você ganhe algo com uma pausa para loops maiores.

String.reverse converte implicitamente em RichString, e é por isso que eu faço duas reversões extras. :) Uma abordagem mais matemática pode ser mais elegante.

Usuário desconhecido
fonte
2

O breakablepacote de terceiros é uma alternativa possível

https://github.com/erikerlandson/breakable

Código de exemplo:

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
fonte
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 quebrar o loop, usando a classe Breaks. Declarando o loop como quebrável.

Parvathirajan Natarajan
fonte
2

Simplesmente podemos fazer em 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;
       } } } } }

resultado :

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

Ironicamente, a invasão do Scala scala.util.control.Breaksé uma exceção:

def break(): Nothing = { throw breakException }

O melhor conselho é: NÃO use break, continue e vá! Na OMI, elas são as mesmas, más práticas e uma fonte maligna de todos os tipos de problemas (e discussões quentes) e, finalmente, "consideradas prejudiciais". Bloco de código estruturado, também neste exemplo as quebras são supérfluas. Nosso Edsger W. Dijkstra † escreveu:

A qualidade dos programadores é uma função decrescente da densidade de instruções go nos programas que eles produzem.

Epicurista
fonte
1

Eu tenho uma situação como o código abaixo

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

Estou usando uma java lib e o mecanismo é que o ctx.read lança uma exceção quando não encontra nada. Eu estava preso na situação em que: eu tenho que interromper o loop quando uma exceção foi lançada, mas scala.util.control.Breaks.break usando Exception para interromper o loop, e estava no bloco catch e foi capturado.

Eu tenho uma maneira feia de resolver isso: faça o loop pela primeira vez e obtenha a contagem do comprimento real. e use-o para o segundo loop.

fazer uma pausa no Scala não é tão bom quando você está usando algumas bibliotecas java.

Lucas Liu
fonte
1

Eu sou novo no Scala, mas que tal isso para evitar exceções e 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);

use-o assim:

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

se você não quer quebrar:

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

O uso inteligente do findmétodo de coleta fará o truque para você.

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

Abaixo está o código para quebrar um loop de uma maneira simples

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

Não sei o quanto o estilo Scala mudou nos últimos 9 anos, mas achei interessante o uso da maioria das respostas existentes varsou a recursão difícil de ler. A chave para sair mais cedo é usar uma coleção lenta para gerar seus possíveis candidatos e, em seguida, verifique a condição separadamente. Para gerar os produtos:

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

Em seguida, encontre o primeiro palíndromo dessa visualização sem gerar todas as combinações:

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

Para encontrar o maior palíndromo (embora a preguiça não lhe compre muito, porque você precisa verificar a lista inteira):

palindromes.max

Seu código original está na verdade verificando o primeiro palíndromo maior que um produto subseqüente, o mesmo que verificando o primeiro palíndromo, exceto em uma condição de limite estranha que eu acho que você não pretendia. Os produtos não estão diminuindo estritamente monotonicamente. Por exemplo, 998*998é maior que 999*997, mas aparece muito mais tarde nos loops.

De qualquer forma, a vantagem da geração preguiçosa e da verificação de condição separadas é que você a escreve da mesma maneira que está usando a lista inteira, mas gera apenas o necessário. Você meio que consegue o melhor dos dois mundos.

Karl Bielefeldt
fonte