Bagaimana cara keluar dari loop di Scala?


276

Bagaimana cara memutus loop?

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

Bagaimana cara mengubah sarang untuk loop menjadi rekursi ekor?

Dari Scala Talk di FOSDEM 2009 http://www.slideshare.net/Odersky/fosdem-2009-1013261 di halaman 22:

Istirahat dan lanjutkan Scala tidak memilikinya. Mengapa? Mereka sedikit penting; lebih baik gunakan banyak fungsi yang lebih kecil. Masalah bagaimana berinteraksi dengan penutupan. Mereka tidak dibutuhkan!

Apa penjelasannya?


Perbandingan Anda membutuhkan tanda sama dengan kedua: if (product.toString == product.toString.reverse) atau mungkin dengan equals-Method-call.
pengguna tidak diketahui

ya, saya melewatkan yang itu ketika saya mengetiknya
TiansHUo

Saya tahu saya membangkitkan pertanyaan lama tapi saya ingin tahu apa tujuan kode ini? Saya pertama kali berpikir bahwa Anda mencoba untuk menemukan produk "palindrome" terbesar yang mungkin dengan kombinasi yang diberikan idan j. Jika kode ini berjalan sampai selesai tanpa keluar dari loop, hasilnya adalah 906609tetapi dengan keluar dari loop lebih awal, hasilnya adalah 90909dengan keluar dari loop tidak membuat kode "lebih efisien" karena mengubah hasil.
Ryan H.

Jawaban:


371

Anda memiliki tiga (atau lebih) opsi untuk keluar dari loop.

Misalkan Anda ingin menjumlahkan angka sampai total lebih besar dari 1000. Anda mencoba

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

kecuali Anda ingin berhenti kapan (jumlah> 1000).

Apa yang harus dilakukan? Ada beberapa opsi.

(1a) Gunakan beberapa konstruk yang mencakup persyaratan yang Anda uji.

var sum = 0
(0 to 1000).iterator.takeWhile(_ => sum < 1000).foreach(i => sum+=i)

(peringatan - ini tergantung pada detail bagaimana tes takeWhile dan foreach disisipkan selama evaluasi, dan mungkin tidak boleh digunakan dalam praktik!).

(1b) Gunakan rekursi ekor sebagai ganti for loop, manfaatkan betapa mudahnya menulis metode baru dalam Scala:

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

(1c) Kembali menggunakan loop sementara

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

(2) Melempar pengecualian.

object AllDone extends Exception { }
var sum = 0
try {
  for (i <- 0 to 1000) { sum += i; if (sum>=1000) throw AllDone }
} catch {
  case AllDone =>
}

(2a) Dalam Scala 2.8+ ini sudah dipaket sebelumnya scala.util.control.Breaksmenggunakan sintaks yang sangat mirip dengan istirahat lama Anda yang akrab dari C / Java:

import scala.util.control.Breaks._
var sum = 0
breakable { for (i <- 0 to 1000) {
  sum += i
  if (sum >= 1000) break
} }

(3) Masukkan kode ke dalam metode dan gunakan kembali.

var sum = 0
def findSum { for (i <- 0 to 1000) { sum += i; if (sum>=1000) return } }
findSum

Ini sengaja dibuat tidak terlalu mudah untuk setidaknya tiga alasan yang bisa saya pikirkan. Pertama, dalam blok kode besar, mudah untuk mengabaikan pernyataan "lanjutkan" dan "hancurkan", atau untuk berpikir Anda keluar lebih atau kurang dari yang sebenarnya, atau perlu mematahkan dua loop yang tidak dapat Anda lakukan tetap mudah - sehingga penggunaan standar, meskipun praktis, memiliki masalah, dan karenanya Anda harus mencoba menyusun kode dengan cara yang berbeda. Kedua, Scala memiliki semua jenis sarang yang mungkin tidak Anda sadari, jadi jika Anda bisa keluar dari masalah, Anda mungkin akan terkejut dengan di mana aliran kode berakhir (terutama dengan penutupan). Ketiga, sebagian besar "loop" Scala sebenarnya bukan loop normal - mereka adalah panggilan metode yang memiliki loop sendiri,Seperti lingkaran, sulit untuk menghasilkan cara yang konsisten untuk mengetahui apa yang harus "istirahat" dan sejenisnya. Jadi, agar konsisten, hal yang bijaksana untuk dilakukan adalah tidak memiliki "istirahat" sama sekali.

Catatan : Ada persamaan fungsional semua ini di mana Anda mengembalikan nilai sumalih - alih memutasikannya. Ini adalah Scala yang lebih idiomatis. Namun, logikanya tetap sama. ( returnmenjadi return x, dll.).


9
Pengecualian, meskipun sepenuhnya benar bahwa Anda dapat melempar pengecualian, ini bisa dibilang penyalahgunaan mekanisme pengecualian (lihat Java Efektif). Pengecualian benar-benar untuk menunjukkan situasi yang benar-benar tidak terduga dan / atau memerlukan pelarian drastis dari kode, yaitu beberapa jenis kesalahan. Terlepas dari itu, mereka tentu sangat lambat (tidak yakin tentang situasi saat ini) karena ada sedikit alasan bagi JVM untuk mengoptimalkannya.
Jonathan

28
@ Jonathan - Pengecualian hanya lambat jika Anda perlu menghitung jejak tumpukan - perhatikan bagaimana saya membuat pengecualian statis untuk melempar bukannya menghasilkan satu dengan cepat! Dan mereka adalah konstruksi kontrol yang benar-benar valid; mereka digunakan di banyak tempat di seluruh perpustakaan Scala, karena mereka benar-benar satu-satunya cara Anda dapat kembali melalui berbagai metode (yang jika Anda memiliki tumpukan penutupan adalah sesuatu yang kadang-kadang perlu Anda lakukan).
Rex Kerr

18
@Rex Kerr, Anda menunjukkan kelemahan break construct (saya tidak setuju dengan mereka), tapi kemudian Anda menyarankan menggunakan pengecualian untuk alur kerja normal! Keluar dari loop bukan merupakan kasus luar biasa, itu adalah bagian dari algoritma, ini bukan kasus penulisan ke file yang tidak ada (misalnya). Jadi singkatnya disarankan "penyembuhan" lebih buruk daripada "penyakit" itu sendiri. Dan ketika saya mempertimbangkan untuk melemparkan pengecualian nyata di breakablebagian ... dan semua rintangan itu hanya untuk menghindari kejahatan break, hmm ;-) Anda harus mengakui, hidup ini ironis.
greenoldman

17
@ Macias - Maaf, kesalahan saya. JVM menggunakan Throwables untuk mengontrol aliran. Lebih baik? Hanya karena mereka biasanya digunakan untuk mendukung penanganan pengecualian tidak berarti mereka hanya dapat digunakan untuk penanganan pengecualian. Kembali ke lokasi yang ditentukan dari dalam penutupan sama seperti melemparkan pengecualian dalam hal aliran kontrol. Maka, tidak mengherankan bahwa inilah mekanisme yang digunakan.
Rex Kerr

14
@RexKerr Yah, untuk apa nilainya Anda meyakinkan saya. Biasanya saya menjadi orang yang menentang Pengecualian untuk aliran program normal, tetapi dua alasan utama tidak berlaku di sini. Itu adalah: (1) mereka lambat [tidak ketika digunakan dengan cara ini], dan (2) mereka menyarankan perilaku luar biasa kepada seseorang yang membaca kode Anda [tidak jika perpustakaan Anda memungkinkan Anda memanggil mereka break] Jika terlihat seperti a breakdan kinerjanya seperti break, sejauh yang saya ketahui itu break.
Tim Goodman

66

Ini telah berubah di Scala 2.8 yang memiliki mekanisme untuk menggunakan jeda. Anda sekarang dapat melakukan hal berikut:

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
Apakah ini menggunakan pengecualian di bawah tenda?
Mike

Ini menggunakan Scala sebagai bahasa prosedural yang mengabaikan keunggulan pemrograman fungsional (yaitu rekursi ekor). Tidak cantik.
Galder Zamarreño

32
Mike: Ya, Scala melempar pengecualian untuk keluar dari loop. Galder: Ini menjawab pertanyaan yang diposting "Bagaimana cara keluar dari loop di Scala?". Apakah itu 'cantik' atau tidak tidak relevan.
hohonuuli

2
@hohonuuli, jadi di blok try-catch tidak akan rusak, kan?
greenoldman

2
@ Galder Zamarreño Mengapa rekursi ekor merupakan keuntungan dalam kasus ini? Bukankah ini hanya sebuah optimasi (siapa aplikasi yang disembunyikan untuk pendatang baru dan membingungkan diterapkan untuk yang berpengalaman). Apakah ada manfaat untuk rekursi ekor dalam contoh ini?
user48956

33

Tidak pernah merupakan ide yang baik untuk keluar dari for-loop. Jika Anda menggunakan for-loop itu berarti Anda tahu berapa kali Anda ingin mengulanginya. Gunakan loop sementara dengan 2 kondisi.

sebagai contoh

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

2
Ini yang saya rasakan adalah cara yang tepat untuk keluar dari loop di Scala. Apakah ada yang salah dengan jawaban ini? (mengingat jumlah upvotes yang rendah).
Jus12

1
memang sederhana dan lebih mudah dibaca. bahkan breakable - break thingy benar, itu terlihat jelek dan memiliki masalah dengan di dalam try-catch. Meskipun solusi Anda tidak bekerja dengan foreach, saya akan memilih Anda, menghormati kesederhanaan.
yerlilbilgin

13

Untuk menambahkan Rex Kerr, jawab dengan cara lain:

  • (1c) Anda juga dapat menggunakan pelindung di lingkaran Anda:

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

30
Saya tidak memasukkan ini sebagai opsi karena itu tidak benar-benar memutus loop - itu berjalan melalui semua itu, tetapi pernyataan if gagal pada setiap iterasi setelah jumlahnya cukup tinggi, jadi itu hanya satu-jika-pernyataan bernilai kerja setiap kali. Sayangnya, tergantung pada bagaimana Anda telah menulis loop, itu bisa jadi banyak pekerjaan.
Rex Kerr

@RexKerr: Bukankah kompiler akan mengoptimalkannya? Tidakkah itu akan dioptimalkan jika tidak selama menjalankan pertama kemudian selama JIT.
Maciej Piechotka

5
@MaciejPiechotka - Kompilator JIT umumnya tidak mengandung logika yang cukup canggih untuk mengenali bahwa pernyataan-if pada variabel yang berubah akan selalu (dalam situasi khusus khusus ini) menghasilkan false dan dengan demikian dapat dihilangkan.
Rex Kerr

6

Karena belum ada breakdi Scala, Anda bisa mencoba menyelesaikan masalah ini dengan menggunakan- returnpernyataan. Oleh karena itu Anda perlu menempatkan loop batin Anda ke suatu fungsi, jika tidak pengembalian akan melewati seluruh loop

Namun Scala 2.8 termasuk cara untuk istirahat

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


maaf, tapi saya hanya ingin keluar dari lingkaran dalam. Anda tidak menyiratkan bahwa saya harus menjalankannya?
TiansHUo

Maaf, seharusnya sudah diklarifikasi. Yakin menggunakan pengembalian berarti Anda harus merangkum loop dalam suatu fungsi. Saya sudah mengedit jawaban saya.
Ham Vocke

1
Itu tidak baik sama sekali. Tampaknya Scala tidak suka loop bersarang.
TiansHUo

Sepertinya tidak ada cara yang berbeda. Anda mungkin ingin melihatnya: scala-lang.org/node/257
Ham Vocke

4
@TiansHUo: Mengapa Anda mengatakan bahwa Scala tidak suka loop bersarang ? Anda memiliki masalah yang sama jika Anda mencoba untuk keluar dari satu loop.
Rex Kerr


5

Cukup gunakan loop sementara:

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

5

Suatu pendekatan yang menghasilkan nilai-nilai pada suatu rentang seperti yang kita lakukan berulang-ulang, hingga kondisi putus, alih-alih menghasilkan pertama seluruh rentang dan kemudian mengulanginya, menggunakan Iterator, (terinspirasi oleh penggunaan @RexKerr Stream)

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

ya saya suka. tidak ada alasan yang bisa dipatahkan, saya pikir itu terlihat lebih bagus.
ses

4

Berikut ini adalah versi rekursif ekor. Dibandingkan dengan untuk-pemahaman itu agak samar, memang, tapi saya akan mengatakan itu fungsional :)

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

Seperti yang Anda lihat, fungsi tr adalah padanan dari bagian luar untuk pemahaman, dan tr1 dari bagian dalam. Sama-sama jika Anda tahu cara mengoptimalkan versi saya.


2

Yang dekat dengan solusi Anda adalah ini:

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-iterasi dibuat tanpa ruang lingkup baru, dan pembuatan produk serta kondisinya dilakukan dalam for-statement (bukan ekspresi yang baik - saya tidak menemukan yang lebih baik). Kondisi terbalik yang cukup cepat untuk ukuran masalah itu - mungkin Anda mendapatkan sesuatu dengan istirahat untuk loop yang lebih besar.

String.reverse secara implisit mengkonversi ke RichString, itulah sebabnya saya melakukan 2 pembalikan ekstra. :) Pendekatan yang lebih matematis mungkin lebih elegan.


2

breakablePaket pihak ketiga adalah salah satu alternatif yang memungkinkan

https://github.com/erikerlandson/breakable

Kode contoh:

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

Metode dasar untuk memutus loop, menggunakan kelas Breaks. Dengan mendeklarasikan loop sebagai breakable.


2

Sederhananya yang bisa kita lakukan di scala adalah

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

keluaran:

scala> TestBreak.main(Array())
1
2
3
4
5

1

Ironisnya, pembobolan Scala scala.util.control.Breaksadalah pengecualian:

def break(): Nothing = { throw breakException }

Saran terbaik adalah: JANGAN menggunakan istirahat, lanjutkan dan kebagian! IMO mereka sama, praktik buruk dan sumber jahat dari semua jenis masalah (dan diskusi panas) dan akhirnya "dianggap berbahaya". Blok kode terstruktur, juga dalam contoh ini jeda adalah berlebihan. Edsger W. Dijkstra kami † menulis:

Kualitas programmer adalah fungsi yang menurun dari kepadatan pernyataan pergi di program yang mereka hasilkan.


1

Saya mendapat situasi seperti kode di bawah ini

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

Saya menggunakan java lib dan mekanismenya adalah ctx.read membuang Exception ketika tidak menemukan apa-apa. Saya terjebak dalam situasi itu: Saya harus memutus loop ketika Exception dilemparkan, tetapi scala.util.control.Breaks.break menggunakan Exception untuk memutus loop, dan itu ada di catch block sehingga tertangkap.

Saya punya cara jelek untuk menyelesaikan ini: lakukan perulangan untuk pertama kalinya dan dapatkan hitungan dari panjang sebenarnya. dan gunakan untuk loop kedua.

mengambil istirahat dari Scala tidak begitu baik, ketika Anda menggunakan beberapa lib java.


1

Saya baru di Scala, tetapi bagaimana dengan ini untuk menghindari melemparkan pengecualian dan metode berulang:

object awhile {
def apply(condition: () => Boolean, action: () => breakwhen): Unit = {
    while (condition()) {
        action() match {
            case breakwhen(true)    => return ;
            case _                  => { };
        }
    }
}
case class breakwhen(break:Boolean);

gunakan seperti ini:

var i = 0
awhile(() => i < 20, () => {
    i = i + 1
    breakwhen(i == 5)
});
println(i)

jika Anda tidak ingin istirahat:

awhile(() => i < 20, () => {
    i = i + 1
    breakwhen(false)
});

1

Penggunaan findmetode pengumpulan yang cerdik akan membantu Anda.

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

Di bawah ini adalah kode untuk memutus loop dengan cara sederhana

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

Saya tidak tahu berapa banyak gaya Scala telah berubah dalam 9 tahun terakhir, tetapi saya merasa menarik bahwa sebagian besar jawaban yang ada menggunakan vars, atau sulit untuk membaca rekursi. Kunci untuk keluar lebih awal adalah dengan menggunakan koleksi malas untuk menghasilkan calon yang memungkinkan Anda, kemudian periksa kondisinya secara terpisah. Untuk menghasilkan produk:

val products = for {
  i <- (999 to 1 by -1).view
  j <- (i to 1 by -1).view
} yield (i*j)

Kemudian untuk menemukan palindrome pertama dari tampilan itu tanpa menghasilkan setiap kombinasi:

val palindromes = products filter {p => p.toString == p.toString.reverse}
palindromes.head

Untuk menemukan palindrome terbesar (walaupun kemalasan tidak banyak membeli Anda karena Anda harus memeriksa seluruh daftar):

palindromes.max

Kode asli Anda sebenarnya memeriksa palindrom pertama yang lebih besar dari produk berikutnya, yang sama dengan memeriksa palindrom pertama kecuali dalam kondisi batas aneh yang menurut saya tidak Anda maksudkan. Produk tidak sepenuhnya menurun secara monoton. Misalnya, 998*998lebih besar dari 999*997, tetapi muncul jauh di loop.

Lagi pula, keuntungan dari generasi malas yang terpisah dan kondisi memeriksa adalah Anda menulisnya cukup banyak seperti menggunakan seluruh daftar, tetapi hanya menghasilkan sebanyak yang Anda butuhkan. Anda mendapatkan yang terbaik dari kedua dunia.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.