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

223

Как вырвать цикл?

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 Обсуждение в FOSDEM 2009 http://www.slideshare.net/Odersky/fosdem-2009-1013261 на 22-й странице:

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

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

  • 0
    Для сравнения требуется второй знак равенства: if (product.toString == product.toString.reverse) или, возможно, вызов метода equals.
  • 0
    да, я пропустил этот, когда я набирал его
Показать ещё 1 комментарий
Теги:
for-loop
tail-recursion
break

15 ответов

306
Лучший ответ

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

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

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

кроме того, что вы хотите остановить, когда (суммa > 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

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

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

  • 0
    Есть еще один. Сделайте это методом и return явно.
  • 0
    @ Даниель: Хм, не (3) это покрывает?
Показать ещё 25 комментариев
51

Это изменилось в 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
    Использует ли это исключения под капотом?
  • 0
    Это использует Scala в качестве процедурного языка, игнорируя преимущества функционального программирования (т.е. хвостовую рекурсию). Не красиво
Показать ещё 5 комментариев
19

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

например

var done = false
while (i <= length && !done) {
  if (i > 1000) {
     done = true;
}
  • 2
    Это то, что я считаю правильным способом вырваться из циклов в Scala. Что-то не так с этим ответом? (учитывая небольшое количество голосов).
  • 1
    действительно просто и более читабельно. даже штукатурка - это правильно, она выглядит некрасиво и имеет проблемы с внутренним try-catch. Хотя ваше решение не работает с foreach, я буду голосовать за вашу простоту.
Показать ещё 1 комментарий
13

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

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

     var sum = 0
     for (i <- 0 to 1000 ; if sum<1000) sum += i
    
  • 30
    Я не включил это в качестве опции, потому что он на самом деле не прерывает цикл - он проходит через все это, но оператор if не выполняется на каждой итерации после того, как сумма достаточно высока, поэтому он выполняет только один оператор if ценность работы каждый раз. К сожалению, в зависимости от того, как вы написали цикл, это может быть много работы.
  • 0
    @RexKerr: Разве компилятор не оптимизировал бы его так или иначе? Не будет ли он оптимизирован, если не во время первого запуска, то во время JIT.
Показать ещё 1 комментарий
6

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

Scala 2.8 однако включает способ разрыва

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

  • 0
    извините, но я только хотел разорвать внутренний цикл. Вы не подразумеваете, что я должен поместить это в функцию?
  • 0
    Извините, должен был уточнить это. Конечно, использование возврата означает, что вам нужно заключить цикл в функцию. Я отредактировал свой ответ.
Показать ещё 3 комментария
4

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

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

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

3

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

var sum = 0
for ( i <- Iterator.from(1).takeWhile( _ => sum < 1000) ) sum += i
  • 0
    Да, мне это нравится. не извиняюсь-извините, я думаю, что это выглядит лучше.
3

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

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

Сторонний пакет 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

Близко к вашему решению будет следующее:

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-statement (не хорошее выражение - я не нахожу лучшего). Условие отменено, что довольно быстро для этого размера проблемы - возможно, вы получаете что-то с перерывом для больших циклов.

String.reverse неявно преобразуется в RichString, поэтому я делаю 2 дополнительных обратных.:) Более математический подход может быть более изящным.

1

Как ни странно, Scala break in scala.util.control.Breaks является исключением:

def break(): Nothing = { throw breakException }

Лучший совет: НЕ используйте break, continue и goto! ИМО - это то же самое, плохая практика и злой источник всех проблем (и горячие дискуссии) и, наконец, "считаются вредными". Структурированный блок кода, также в этом примере перерывы являются излишними. Наш Edsger W. Dijkstra † написал:

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

0

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

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

Появилась ситуация, подобная приведенному ниже коду

 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 lib, и механизм заключается в том, что ctx.read выдает исключение, когда он ничего не может найти. Я оказался в ловушке ситуации: я должен разбить цикл, когда был выброшен Exception, но scala.util.control.Breaks.break, используя Exception, чтобы разбить цикл, и он был в блоке catch, таким образом, он был пойман.

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

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню