Как мне выйти из цикла в 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 }
Лучший совет: НЕ используйте перерыв, продолжайте и переходите! ИМО они одинаковы, плохая практика и злой источник всех видов проблем (и горячих дискуссий) и, наконец, "считаются вредными". Блок кода структурирован, также в этом примере разрывы излишни. Наш Эдсгер В. Дейкстра † писал:
Качество программистов - это убывающая функция плотности переходов к операторам в программах, которые они создают.