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


276

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

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-й странице:

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

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


Для сравнения требуется второй знак равенства: if (product.toString == product.toString.reverse) или, возможно, вызов метода equals.
пользователь неизвестен

да, я пропустил этот, когда
набирал

Я знаю, что воскрешаю старый вопрос, но я хотел бы знать, какова цель этого кода? Сначала я подумал, что вы пытаетесь найти самый большой «палиндромный» продукт из возможных комбинаций iи j. Если этот код выполняется до завершения, не выходя из цикла, то результат заключается 906609в том, что преждевременный выход из цикла приводит к 90909тому, что выход из цикла не делает код «более эффективным», поскольку он изменяет результат.
Райан Х.

Ответы:


371

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

Предположим, вы хотите суммировать числа, пока сумма не станет больше 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и т. д.).


9
Что касается исключений, то, хотя вы и можете выдавать исключение, это, вероятно, является злоупотреблением механизмом исключения (см. Эффективная Java). Исключения действительно указывают на ситуации, которые являются действительно неожиданными и / или требуют радикального выхода из кода, то есть ошибок какого-либо рода. Помимо этого, они, конечно, были довольно медленными (не уверены в текущей ситуации), потому что у JVM нет особых причин для их оптимизации.
Джонатан

28
@Jonathan - Исключения являются медленными, только если вам нужно вычислить трассировку стека - обратите внимание, как я создал статическое исключение, чтобы генерировать его вместо генерации на лету! И они являются совершенно допустимой управляющей конструкцией; они используются в разных местах в библиотеке Scala, поскольку на самом деле это единственный способ, которым вы можете вернуться с помощью нескольких методов (что, если у вас есть куча замыканий, иногда нужно делать).
Рекс Керр

18
@Rex Kerr, вы указываете на слабые стороны конструкции break (я с ними не согласен), но затем вы предлагаете использовать исключения для нормального рабочего процесса! Выход из цикла - не исключительный случай, это часть алгоритма, это не случай записи в несуществующий файл (например). Таким образом, вкратце предложенное «излечение» хуже самой «болезни». И когда я подумываю бросить настоящее исключение в breakableраздел ... и все эти обручи, чтобы избежать зла break, хм ;-) Согласись, жизнь иронична.
Гринольдман

17
@macias - Извините, моя ошибка. JVM использует Throwables для управления потоком. Лучше? То, что они обычно используются для поддержки обработки исключений, не означает, что они могут использоваться только для обработки исключений. Возврат к определенному местоположению из замыкания аналогичен выдаче исключения с точки зрения потока управления. Не удивительно, что этот механизм используется.
Рекс Керр

14
@RexKerr Ну, за то, что это стоит, вы меня убедили. Обычно я бы противостоял Исключениям для нормального выполнения программы, но две основные причины здесь не применимы. Это: (1) они медленные [не при использовании таким образом], и (2) они предлагают исключительное поведение тому, кто читает ваш код [не, если ваша библиотека позволяет вам вызывать их break], если она выглядит как a breakи выполняет как break, насколько я понимаю, это break.
Тим Гудман

66

Это изменилось в 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
        }
    }
}

3
Использует ли это исключения под капотом?
Майк

Это использует Scala в качестве процедурного языка, игнорируя преимущества функционального программирования (т.е. хвостовую рекурсию). Не красиво
Галдер Замарреньо

32
Майк: Да, Scala выбрасывает исключение, чтобы выйти из цикла. Galder: Это отвечает на опубликованный вопрос «Как выйти из цикла в Scala?». Неважно, симпатичная она или нет.
Хохонуули

2
@ hohonuuli, значит, в блоке try-catch он не сломается, верно?
Гринольдман

2
@Galder Zamarreño Почему рекурсия хвоста является преимуществом в этом случае? Разве это не просто оптимизация (чье приложение скрыто для новичка и запутанно применено для опытного). Есть ли польза от хвостовой рекурсии в этом примере?
user48956

33

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

например

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

2
Это то, что я считаю правильным способом вырваться из циклов в Scala. Что-то не так с этим ответом? (учитывая небольшое количество голосов).
Jus12

1
действительно просто и более читабельно. даже вещь, которую можно сломать - это правильно, выглядит ужасно и имеет проблемы с внутренним try-catch. Хотя ваше решение не работает с foreach, я буду голосовать за вашу простоту.
yerlilbilgin

13

Чтобы добавить Rex Kerr, ответьте по-другому:

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

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

30
Я не включил это в качестве опции, потому что он на самом деле не прерывает цикл - он проходит через все это, но оператор if завершается неудачей на каждой итерации после того, как сумма достаточно высока, поэтому он выполняет только один оператор if. ценность работы каждый раз. К сожалению, в зависимости от того, как вы написали цикл, это может быть много работы.
Рекс Керр

@RexKerr: Разве компилятор не оптимизировал бы его в любом случае? Не будет ли он оптимизирован, если не во время первого запуска, то во время JIT.
Maciej Piechotka

5
@MaciejPiechotka - JIT-компилятор, как правило, не содержит достаточно сложной логики, чтобы признать, что оператор if для изменяющейся переменной всегда (в этой особой специальной ситуации) всегда возвращает false и поэтому может быть опущен.
Рекс Керр

6

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

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

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


извините, но я только хотел разорвать внутренний цикл. Вы не подразумеваете, что я должен поместить это в функцию?
TiansHUo

Извините, должен был уточнить это. Конечно, использование возврата означает, что вам нужно заключить цикл в функцию. Я отредактировал свой ответ.
Ham Vocke

1
Это совсем не приятно. Кажется, что Scala не любит вложенные циклы.
TiansHUo

Кажется, нет другого пути. Возможно, вы захотите взглянуть на это: scala-lang.org/node/257
Ham Vocke

4
@TiansHUo: Почему вы говорите, что Scala не любит вложенные циклы? У вас те же проблемы, если вы пытаетесь выйти из одного цикла.
Рекс Керр


5

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

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

5

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

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

Да, мне это нравится. не извиняюсь, я думаю, что это выглядит лучше.
SES

4

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

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 внутреннего. Не за что, если вы знаете способ оптимизировать мою версию.


2

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

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 дополнительных реверса. :) Более математический подход может быть более элегантным.


2

Сторонний 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))

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. Объявляя цикл как разрушаемый.


2

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

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

1

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

def break(): Nothing = { throw breakException }

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

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


1

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

 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-библиотеки.


1

Я новичок в 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)
});

1

Умное использование 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)

1

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

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

Я не знаю, насколько сильно изменился стиль 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, но появляется намного позже в циклах.

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.