Як мені вирватися з петлі у 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-й сторінці:

Перервіться і продовжуйте Scala їх не має. Чому? Вони трохи імперативні; краще використовувати багато менших функцій. Пишіть, як взаємодіяти із закриттями. Вони не потрібні!

Яке пояснення?


Для вашого порівняння потрібен другий знак рівності: якщо (product.toString == product.toString.reverse) або, можливо, дорівнює метод-call.
користувач невідомий

так, я пропустив цю, коли я
вводив

Я знаю, що я породжую старе питання, але мені б хотілося знати, яка мета цього коду? Я спершу, хоча ви намагалися знайти найбільший продукт "паліндром" за допомогою комбінацій 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) Використовуйте хвостову рекурсію замість петлі, скориставшись тим, як легко написати новий метод у Scala:

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

(1c) Відмовтеся від використання циклу час

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а не мутуєте його на місці. Це більш ідіоматична Скала. Однак логіка залишається тією ж. ( returnстає return xтощо).


9
Що стосується винятків, хоча суворо вірно, що ви можете кинути виняток, це, мабуть, зловживання механізмом виключень (див. Ефективна Java). Винятки насправді вказують на ситуації, які є справді несподіваними та / або вимагають кардинального відходу від коду, тобто якихось помилок. Окрім цього, вони, звичайно, були досить повільними (не впевнені в ситуації, що склалася), тому що для JVM їх оптимізація мало.
Джонатан

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

18
@Rex Kerr, ви вказуєте на недоліки конструкції розриву (я не згоден з ними), але тоді ви пропонуєте використовувати винятки для нормального робочого процесу! Вихід із циклу не є винятковим випадком, це частина алгоритму, це не випадок запису до неіснуючого файлу (наприклад). Отже, коротше кажучи, «вилікувати» гірше, ніж сама хвороба. І коли я розглядаю, як кинути справжній виняток у breakableрозділ ... і всі ці обручі лише для того, щоб уникнути зла break, хм ;-) Ви повинні визнати, життя іронічне.
greenoldman

17
@macias - Вибачте, моя помилка. JVM використовує Throwables для контролю потоку. Краще? Тільки тому, що вони зазвичай використовуються для підтримки обробки виключень, не означає, що їх можна використовувати лише для обробки винятків. Повернення до визначеного місця зсередини закриття - це як викидання виключення з точки зору потоку управління. Тож не дивно, що це механізм, який використовується.
Рекс Керр

14
@RexKerr Ну, для чого варто, ти мене переконав. Зазвичай я б хотів протистояти Виняткам для нормальної програми, але дві основні причини тут не стосуються. Це: (1) вони повільно [не тоді , коли використовується таким чином], і (2) можуть запропонувати виключне поведінку до кого - то читати ваш код [немає , якщо ваша бібліотека дозволяє назвати їх break] Якщо це виглядає схоже на 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?". Незалежно від того, є "гарним" чи ні.
hohonuuli

2
@hohonuuli, значить, у блоці спробу лову він не зламається, правда?
greenoldman

2
@Galder Zamarreño Чому в цьому випадку рекурсія хвоста є перевагою? Хіба це не просто оптимізація (хто додаток прихований за новачком і заплутано застосовується для досвідчених) Чи є користь для рецидиву хвоста в цьому прикладі?
user48956

32

Ніколи не є хорошою ідеєю вирватися з-за-петлі. Якщо ви використовуєте цикл for, це означає, що ви знаєте, скільки разів ви хочете повторити. Скористайтеся циклом while з двома умовами.

наприклад

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

2
Це те, що я вважаю, це правильний спосіб вирватися з петель у Scala. Чи щось не так у цій відповіді? (зважаючи на низьку кількість оновлень).
липня

1
дійсно простий і читабельніший. навіть зламаний - зламати щось правильно, він виглядає некрасиво і має проблеми з внутрішнім пробним уловом. Хоча ваше рішення не працює з принципом, я проголосую за вас, шануючи простоту.
yerlilbilgin

13

Щоб додати відповідь Рекса Керра іншим способом:

  • (1c) Ви також можете використовувати захист у своєму циклі:

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

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

@RexKerr: Невже компілятор не оптимізував би це? Не буде оптимізовано, якщо не під час першого запуску, то під час JIT.
Maciej Piechotka

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

6

Оскільки breakу Scala ще немає, ви можете спробувати вирішити цю проблему за допомогою return-state. Тому вам потрібно ввести свій внутрішній цикл у функцію, інакше повернення пропустить весь цикл.

Scala 2.8, однак, включає спосіб виходу з ладу

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


вибачте, але мені хотілося лише вирвати внутрішню петлю. Ви не маєте на увазі, що я повинен ввести це у функцію?
TiansHUo

Вибачте, мав би це уточнити. Звичайно, використання повернення означає, що вам потрібно інкапсулювати цикл у функції. Я відредагував свою відповідь.
Хам Вокке

1
Це зовсім не приємно. Здається, що Скала не любить вкладені петлі.
TiansHUo

Схоже, немає іншого способу. Ви можете поглянути на це: scala-lang.org/node/257
Ham Vocke

4
@TiansHUo: Чому ти кажеш, що Скала не любить вкладені петлі? У вас є ті самі проблеми, якщо ви намагаєтеся вирватися з одного циклу.
Рекс Керр



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-ітерація робиться без нового розмаху, а генерування продукту, а також умова виконуються у формулі (не гарний вираз - я не вважаю кращого). Умова повернена, що досить швидко для цього розміру проблеми - можливо, ви отримуєте щось із перервою для великих циклів.

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.util.control.Breaks- виняток:

def break(): Nothing = { throw breakException }

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

Якість програмістів - це зменшується функція щільності переходу до операторів у програмах, які вони виробляють.


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

У мене є некрасивий спосіб вирішити це: зробити цикл вперше і отримати підрахунок реальної довжини. і використовувати його для другої петлі.

зняти перерву від Scala - це не так добре, коли ти використовуєш декілька ява.


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.