Как мне выйти из цикла в Scala?

Как мне разорвать петлю?

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

Как превратить вложенные циклы в хвостовую рекурсию?

Из Scala Talk на FOSDEM 2009 http://www.slideshare.net/Odersky/fosdem-2009-1013261 на 22-й странице:

Разбить и продолжить у Скалы их нет. Зачем? Они немного обязательны; лучше использовать множество мелких функций. Вопрос о том, как взаимодействовать с замыканиями. Они не нужны!

Какое объяснение?

18 ответов

Решение

У вас есть три (или около того) варианта вырваться из петель.

Предположим, что вы хотите суммировать числа, пока сумма не станет больше 1000. Вы пытаетесь

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

кроме того, что вы хотите остановить, когда (сумма> 1000).

Что делать? Есть несколько вариантов.

(1a) Используйте некоторую конструкцию, включающую проверяемое условие.

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

(предупреждение - это зависит от деталей того, как тест takeWhile и foreach чередуются во время оценки, и, вероятно, не должны использоваться на практике!).

(1b) Используйте хвостовую рекурсию вместо цикла for, используя преимущества того, как легко написать новый метод в Scala:

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

(1c) вернуться к использованию цикла while

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

(2) Брось исключение.

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

(2a) В Scala 2.8+ это уже предварительно упаковано в scala.util.control.Breaks используя синтаксис, который очень похож на ваш знакомый старый перерыв в C / Java:

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

(3) Поместите код в метод и используйте return.

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

Это намеренно сделано не слишком легко по крайней мере по трем причинам, о которых я могу думать. Во-первых, в больших блоках кода легко пропустить операторы "continue" и "break", или думать, что вы выходите за рамки большего или меньшего, чем вы есть на самом деле, или вам нужно разбить два цикла, которые вы не можете сделать в любом случае легко - поэтому стандартное использование, хотя и удобно, имеет свои проблемы, и поэтому вы должны попытаться структурировать свой код по-другому. Во-вторых, в Scala есть все виды вложений, которые вы, вероятно, даже не замечаете, поэтому, если вы можете что-то разорвать, вы, вероятно, будете удивлены тем, где закончился поток кода (особенно с замыканиями). В-третьих, большинство "циклов" Scala на самом деле не являются обычными циклами - это вызовы методов, которые имеют свой собственный цикл, или они являются рекурсией, которая на самом деле может быть циклом, а может и не быть - и хотя они действуют как петли, это сложно придумать последовательный способ узнать, что должен делать "перерыв" и тому подобное. Поэтому, чтобы быть последовательным, разумнее всего не иметь "перерыва".

Примечание: есть функциональные эквиваленты всех этих, где вы возвращаете значение sum вместо того, чтобы изменить его на месте. Это более идиоматические Scala. Однако логика остается прежней. (return становится return x, так далее.).

Это изменилось в Scala 2.8, в которой есть механизм для использования разрывов. Теперь вы можете сделать следующее:

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

Никогда не стоит выходить из цикла. Если вы используете цикл for, это означает, что вы знаете, сколько раз вы хотите выполнить итерацию. Используйте цикл while с 2 условиями.

например

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

Чтобы добавить Рекс Керр, ответьте по-другому:

  • (1c) Вы также можете использовать охрану в вашей петле:

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

Просто мы можем сделать в Скала

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

выход:

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

Так как нет break в Scala вы можете попытаться решить эту проблему с помощью return-заявление. Поэтому вам нужно поместить свой внутренний цикл в функцию, иначе возврат пропустит весь цикл.

Scala 2.8, однако, включает в себя способ сломать

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

Подход, который генерирует значения в диапазоне во время итерации, вплоть до нарушающего условия, вместо генерации сначала всего диапазона, а затем итерации по нему, используя Iterator, (вдохновленный @RexKerr использованием Stream)

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

Просто используйте цикл while:

var (i, sum) = (0, 0)
while (sum < 1000) {
  sum += i
  i += 1
}
// 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;
   }
}

использовать модуль разрыва http://www.tutorialspoint.com/scala/scala_break_statement.htm

Вот хвостовая рекурсивная версия. По сравнению с "для понимания" это немного загадочно, правда, но я бы сказал, его функционал:)

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

Как вы можете видеть, функция tr является аналогом внешнего понимания и tr1 внутреннего. Не за что, если вы знаете способ оптимизировать мою версию.

Сторонний breakable пакет является одной из возможных альтернатив

https://github.com/erikerlandson/breakable

Пример кода:

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

Близко к вашему решению было бы это:

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)

J-итерация выполняется без новой области видимости, а генерация продукта, а также условие выполняются в операторе for (не очень хорошее выражение - лучшего я не нахожу). Условие меняется на противоположное, что довольно быстро для такого размера задачи - может быть, вы получите что-то с разрывом для больших циклов.

String.reverse неявно преобразуется в RichString, поэтому я делаю 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.
   }
}

Основной метод для разрыва цикла, используя класс Breaks. Объявляя цикл как разрушаемый.

Я новичок в Scala, но как насчет этого, чтобы избежать создания исключений и повторяющихся методов:

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

используйте это так:

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

если не хочешь ломаться

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

Я получил ситуацию, как код ниже

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

Я использую Java-библиотеку, и механизм заключается в том, что ctx.read создает исключение, когда ничего не может найти. Я попал в ловушку ситуации, в которой: я должен был разорвать цикл, когда было сгенерировано исключение, но scala.util.control.Breaks.break использовал исключение, чтобы разорвать цикл, и это было в блоке catch, поэтому он был перехвачен.

Я получил неприятный способ решить эту проблему: сделать цикл в первый раз и получить счет реальной длины. и использовать его для второго цикла.

Отрываться от Скалы не очень хорошо, когда вы используете несколько java-библиотек.

Умное использование find Метод для сбора сделает свое дело для вас.

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)

Ниже приведен код для простого разрыва цикла

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"

        }
      }
    }
  }
}

Я не знаю, насколько изменился стиль Scala за последние 9 лет, но мне показалось интересным, что в большинстве существующих ответов используется vars, или трудночитаемую рекурсию. Ключ к раннему выходу - использовать ленивую коллекцию для генерации ваших возможных кандидатов, а затем проверять условие отдельно. Для создания продуктов:

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

Затем, чтобы найти первый палиндром из этого представления без создания каждой комбинации:

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

Чтобы найти самый большой палиндром (хотя лень вам мало, потому что вам все равно придется проверять весь список):

palindromes.max

Ваш исходный код фактически проверяет первый палиндром, который больше, чем последующий продукт, что аналогично проверке первого палиндрома, за исключением странного граничного условия, которое, я не думаю, вы намеревались. Продукция строго не монотонно убывает. Например,998*998 больше, чем 999*997, но появляется гораздо позже в циклах.

В любом случае, преимущество разделенной отложенной генерации и проверки условий заключается в том, что вы пишете ее так же, как и весь список, но она генерирует ровно столько, сколько вам нужно. Вы как бы получаете лучшее из обоих миров.

По иронии судьбы Scala взломать scala.util.control.Breaks исключение:

def break(): Nothing = { throw breakException }

Лучший совет: НЕ используйте перерыв, продолжайте и переходите! ИМО они одинаковы, плохая практика и злой источник всех видов проблем (и горячих дискуссий) и, наконец, "считаются вредными". Блок кода структурирован, также в этом примере разрывы излишни. Наш Эдсгер В. Дейкстра † писал:

Качество программистов - это убывающая функция плотности переходов к операторам в программах, которые они создают.

Другие вопросы по тегам