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

ネストされたforループをテール再帰に変換するにはどうすればよいですか?

22ページ目のFOSDEM 2009 http://www.slideshare.net/Odersky/fosdem-2009-1013261でのScala Talkから:

中断して続行Scalaにはありません。どうして?それらは少し必須です。多くの小さな関数をより適切に使用するクロージャとの相互作用方法を発行します。それらは必要ありません!

説明は何ですか?


比較には、2番目の等号(if(product.toString == product.toString.reverse)または多分equals-Method-callが必要です。
ユーザー不明

ええ、タイプしているときにそれを
見逃し

私は古い質問を復活させていることを知っていますが、このコードの目的が何であるか知りたいですか?私は、あなたが与えられたとの組み合わせで可能最大級の「回文」の製品を検索しようとした最初のものかかわらず、ij。このコードがループから抜け出すことなく最後まで実行された場合、結果はループから抜け出すことになりますが、結果は906609ループから90909抜け出すことによってコードが「効率的に」なり、結果が変更されるためです。
Ryan H.

回答:


371

ループから抜け出すには、3つ(またはそれ以上)のオプションがあります。

合計が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

これは、少なくとも3つの理由が考えられるため、意図的に簡単ではありません。まず、大きなコードブロックでは、 "continue"および "break"ステートメントを見落としたり、実際よりも多いまたは少ないものから抜け出していると考えたり、実行できない2つのループを壊したりする必要があります。とにかく簡単-標準的な使用法は便利ですが、問題があるため、コードを別の方法で構造化する必要があります。第2に、Scalaにはおそらく気付かないほどのあらゆる種類のネストがあるため、問題が発生する可能性がある場合は、コードフローが(特にクロージャーで)どこに到達したかに驚かれるでしょう。第三に、Scalaの「ループ」のほとんどは実際には通常のループではありません。これらは独自のループを持つメソッド呼び出しです。ループのように、「ブレーク」などが何をすべきかを知るための一貫した方法を思いつくことは困難です。したがって、一貫性を保つために、賢明なことは、「ブレーク」をまったく行わないことです。

:これらすべての機能に相当するものがあり、その場所で値を変更するのでsumはなく、の値を返します。これらはより慣用的なScalaです。ただし、ロジックは変わりません。(returnとなりreturn xます)。


9
例外については、例外をスローできることは厳密に当てはまりますが、これは間違いなく例外メカニズムの悪用です(効果的なJavaを参照)。例外とは、本当に予期しない状況、またはコードからの大幅なエスケープが必要な状況、つまり何らかのエラーを示す状況です。それとは別に、JVMがそれらを最適化する理由はほとんどないため、以前はかなり低速でした(現在の状況については不明)。
ジョナサン

28
@Jonathan-スタックトレースを計算する必要がある場合にのみ、例外が遅くなります-オンザフライで生成する代わりに、スローする静的例外を作成した方法に注意してください!そして、それらは完全に有効な制御構造です。これらは、Scalaライブラリ全体の複数の場所で使用されています。これは、実際に複数のメソッドを介して戻ることができる唯一の方法だからです(クロージャの山がある場合に必要なことがあります)。
レックスカー、

18
@Rex Kerr、あなたはbreak構造の弱点を指摘しています(私はそれらに同意しません)が、通常のワークフローでは例外を使用することを提案します!ループの終了は例外的なケースではありません。それはアルゴリズムの一部であり、存在しないファイルへの書き込みのケースではありません(たとえば)。要するに、提案された「治癒」は「病気」自体よりも悪いです。そして、私がbreakableセクションで実際の例外を投げることを検討するとき...そしてbreak、悪を避けるためにそれらすべてのフープ、うーん;-)あなたは認めなければならない、人生は皮肉です。
greenoldman 2012年

17
@macias-すみません、私の間違い。JVMは制御フローにThrowableを使用しています。いい?例外処理をサポートするために通常使用されるからといって、例外処理にのみ使用できるわけではありません。クロージャー内から定義された場所に戻ることは、制御フローの点で例外をスローするのと同じです。したがって、これが使用されるメカニズムであることは驚くにあたりません。
レックスカー

14
@RexKerrまあ、それはあなたが私を納得させる価値があるもののために。通常、私は通常のプログラムフローの例外に対処するために1つになりますが、2つの主な理由はここでは当てはまりません。それらは次のとおりです:(1)遅い(このように使用されていない場合)、(2)彼らはあなたのコードを読んでいる誰かに例外的な振る舞いを提案しますbreak、それは同様の見れば] break、それを実行しますのように、break私に関する限り、それはですbreak
Tim Goodman

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を使用しています。きれいじゃない。
GalderZamarreño11年

32
マイク:はい、Scalaはループから抜け出すために例外をスローしています。Galder:これは、投稿された質問「Scalaでループから抜け出すにはどうすればよいですか?」に答えます。それが「かわいい」かどうかは関係ありません。
hohonuuli、2011年

2
@hohonuuli、それはtry-catchブロックにあるので壊れませんよね?
greenoldman 2012年

2
@GalderZamarreñoこの場合、末尾再帰が有利なのはなぜですか?それは単に最適化ではありませんか(誰のアプリケーションが初心者には隠されており、経験豊富なユーザーに混乱して適用されています)。この例では、末尾再帰の利点はありますか?
user48956 2016

32

forループから抜け出すことは決して良い考えではありません。forループを使用している場合は、反復する回数がわかっていることを意味します。2つの条件を持つwhileループを使用します。

例えば

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

2
これは、Scalaでループから抜け出すための正しい方法だと私が感じていることです。この答えに問題はありますか?(賛成票の数が少ないことを考慮する)。
Jus12

1
確かにシンプルで読みやすいです。壊れやすいものでも-壊れやすいものは正しいです、それは醜く見え、内部トライキャッチで問題があります。あなたのソリューションはforeachでは機能しませんが、単純さを尊重して投票します。
エールリルビルギン2016

13

レックスカーを追加するには別の方法で答えてください:

  • (1c)ループでガードを使用することもできます。

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

30
これは実際にはループを壊さないので、これをオプションとして含めませんでした-それはすべてを実行しますが、合計が十分に高くなった後、繰り返しごとにifステートメントが失敗するため、1つのifステートメントのみが実行されます毎回の作業の価値。残念ながら、ループの記述方法によっては、多くの作業が必要になる場合があります。
Rex Kerr

@RexKerr:コンパイラはとにかくそれを最適化しませんか?最初の実行中でなければJIT中に最適化されませんか。
Maciej Piechotka

5
@MaciejPiechotka-通常、JITコンパイラーには、変化する変数のifステートメントが常に(この特別な状況で)falseを返すため、省略できることを認識するのに十分な高度なロジックが含まれていません。
Rex Kerr

6

何があるのでbreak、まだScalaで、あなたが使用してこの問題を解決しようとすることができませんでしたreturn-statementを。したがって、内部ループを関数に入れる必要があります。そうしないと、リターンによってループ全体がスキップされます。

Scala 2.8には、ブレークする方法が含まれています

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


申し訳ありませんが、私は内側のループを抜け出したいだけでした。あなたは私がそれを関数に入れるべきだという意味ではありませんか?
TiansHUo 2010

すみません、それを明確にすべきでした。確かにreturnを使用すると、ループを関数にカプセル化する必要があります。回答を編集しました。
Ham Vocke

1
それは全然良くない。Scalaはネストされたループを好まないようです。
TiansHUo 2010

別の方法はないようです。あなたはこれを見てみたいかもしれません:scala-lang.org/node/257
Ham Vocke

4
@TiansHUo:なぜScalaはネストされたループを好まないと言うのですか?単一のループから抜け出そうとする場合、同じ問題があります。
Rex Kerr


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

はい私はそれが好き。壊れやすい言い訳はありません、私はそれがより良く見えると思います。
2017年

4

これは末尾再帰バージョンです。for-comprehensionsと比較して、それは確かに少し不可解ですが、私はその機能を言うでしょう:)

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関数は外側のfor内包表記に対応し、tr1は内側のfor内包表記に対応しています。私のバージョンを最適化する方法を知っていれば大歓迎です。


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-iterationは新しいスコープなしで作成され、製品の生成と条件はforステートメントで行われます(良い表現ではありません-良い表現は見つかりません)。条件が逆転し、その問題のサイズではかなり高速になります。おそらく、より大きなループの中断で何かを得ることができます。

String.reverseは暗黙的にRichStringに変換されます。そのため、2つの追加の逆を行います。:)より数学的なアプローチの方がエレガントかもしれません。


2

サードパーティbreakableパッケージは1つの可能な代替手段です

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 }

最善のアドバイスは次のとおりです:休憩を使用しないでください。IMOそれらは同じ、悪い習慣であり、あらゆる種類の問題(および激しい議論)の邪悪な原因であり、最終的に「有害であると見なされます」。構造化されたコードブロック。この例でも、ブレークは不必要です。Edsger W. Dijkstra†は次のように書いています。

プログラマーの品質は、作成するプログラムのgo toステートメントの密度の減少関数です。


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がループを解除し、catchブロックにあるため、キャッチされました。

私はこれを解決するための醜い方法を得ました:初めてループを実行して、実際の長さのカウントを取得します。2番目のループに使用します。

あなたがいくつかのJavaライブラリを使用している場合、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

過去9年間でScalaのスタイルがどの程度変化したかはわかりませんが、既存の回答のほとんどがを使用している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が、ループのずっと後に現れます。

とにかく、分離された遅延生成と条件チェックの利点は、リスト全体を使用するのとほぼ同じように記述できることですが、必要なだけ生成されます。あなたは一種の両方の世界のベストを得ます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.