Scala'daki bir döngüden nasıl çıkabilirim?


276

Bir döngüyü nasıl kesebilirim?

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

Döngüler için iç içe nasıl kuyruk yinelemesine dönüştürebilirim?

22. sayfadaki FOSDEM 2009 http://www.slideshare.net/Odersky/fosdem-2009-1013261 adresindeki Scala Talk'tan :

Kır ve devam et Scala'da yok. Neden? Biraz zorunludurlar; birçok küçük işlevi daha iyi kullanın Kapaklarla nasıl etkileşim kuracağınızı sorun. Onlara gerek yok!

Açıklama nedir?


Karşılaştırmanızın ikinci eşittir işaretine ihtiyacı vardır: if (product.toString == product.toString.reverse) veya belki de eşittir-Method-call.
kullanıcı bilinmiyor

Evet, ben yazarken bunu özledim
TiansHUo

Ben eski bir soru diriliyorum biliyorum ama bu kodun amacı nedir bilmek isterim? Ben size verilen kombinasyonları ile mümkün olan en "palindrom" ürünü bulmak için çalıştıklarını öncelikle bu olsa ive j. Bu kod döngüden çıkmadan tamamlanırsa, sonuç 906609döngüden erkenden 90909koparak sonuç, döngüden koparmanın sonucu değiştirdiği için kodu "daha verimli" yapmamasıdır.
Ryan

Yanıtlar:


371

Döngülerden kurtulmak için üç seçeneğiniz vardır.

Toplam 1000'den büyük olana kadar sayıları toplamak istediğinizi varsayalım.

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

dışında durmak istediğinizde (toplam> 1000).

Ne yapalım? Birkaç seçenek var.

(1a) Test ettiğiniz bir koşul içeren bir yapı kullanın.

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

(uyarı - bu, takeWhile testinin ve foreach'in değerlendirme sırasında nasıl serpiştirildiğine ve muhtemelen pratikte kullanılmaması gerektiğine dair ayrıntılara bağlıdır!).

(1b) Scala'da yeni bir yöntem yazmanın ne kadar kolay olduğundan yararlanarak, for döngüsü yerine kuyruk özyineleme kullanın:

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

(1c) while döngüsü kullanmaya geri dön

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

(2) Bir istisna atın.

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+ sürümünde bu zaten scala.util.control.BreaksC / Java'dan tanıdık eski kopuşunuza benzeyen sözdizimi kullanılarak önceden paketlenmiştir :

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

(3) Kodu bir yönteme ekleyin ve return kullanın.

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

Bu, düşünebildiğim en az üç nedenden ötürü kasıtlı olarak çok kolay değil. Birincisi, büyük kod bloklarında, "devam" ve "kır" ifadelerini göz ardı etmek veya gerçekte olduğundan daha fazla veya daha az koptuğunuzu düşünmek veya yapamayacağınız iki döngüyü kırmak kolaydır kolayca - standart kullanım, kullanışlı olsa da, sorunları vardır ve bu nedenle kodunuzu farklı bir şekilde yapılandırmaya çalışmalısınız. İkincisi, Scala muhtemelen farkına bile varmayacağınız her türlü yuvaya sahiptir, bu yüzden bir şeylerden kurtulabilirseniz, kod akışının nereye gittiğine (özellikle kapaklarla) şaşıracaksınız. Üçüncüsü, Scala'nın "döngülerinin" çoğu aslında normal döngüler değildir - kendi döngüleri olan yöntem çağrılarıdır,looplike, "mola" ve benzerlerinin ne yapması gerektiğini bilmek için tutarlı bir yol bulmak zor. Dolayısıyla, tutarlı olmak için yapılacak en akıllıca şey hiç bir "mola" vermemek.

Not : Bunların sumyerine mutasyon yapmak yerine değerini döndürdüğünüz tüm bunların işlevsel eşdeğerleri vardır . Bunlar daha deyimsel Scala. Ancak, mantık aynı kalır. ( returnolur return xvb.).


9
İstisnalar, istisna atabileceğiniz konusunda kesinlikle doğru olmasına rağmen, bu istisna mekanizmasının kötüye kullanılmasıdır (bkz. İstisnalar, gerçekten beklenmedik olan ve / veya koddan ciddi bir kaçış gerektiren durumları, yani bir tür hataları göstermektir. Bunun dışında, kesinlikle oldukça yavaşlardı (mevcut durumdan emin değiller) çünkü JVM'lerin onları optimize etmeleri için çok az neden var.
Jonathan

28
@Jonathan - İstisnalar yalnızca bir yığın izlemesi hesaplamanız gerekiyorsa yavaştır - anında bir tane oluşturmak yerine atmak için nasıl statik bir istisna oluşturduğuma dikkat edin! Ve tamamen geçerli bir kontrol yapısıdır; Scala kütüphanesi boyunca birden fazla yerde kullanılırlar, çünkü bunlar birden fazla yöntemle geri dönebilmenin tek yolu (bunlar, eğer bir yığın kapınız varsa, bazen yapmanız gereken bir şeydir).
Rex Kerr

18
@Rex Kerr, mola yapısının zayıflıklarına işaret ediyorsunuz (onlarla aynı fikirde değilim), ama sonra normal iş akışı için istisnalar kullanmanızı öneririz ! Bir döngüden çıkmak istisnai bir durum değildir, algoritmanın bir parçasıdır, var olmayan bir dosyaya yazma söz konusu değildir (örneğin). Kısacası önerilen "tedavi", "hastalığın" kendisinden daha kötüdür. Ve breakablebölümde gerçek bir istisna atmayı düşündüğümde ... ve tüm bu çemberleri kötülükten kaçınmak için break, hmm ;-) Kabul etmelisin, hayat ironik.
greenoldman

17
@macias - Üzgünüm, hatam. JVM kontrol akışı için Throwables kullanıyor. Daha iyi? Genellikle istisna işlemeyi desteklemek için kullanıldıkları, yalnızca istisna işlemede kullanılabilecekleri anlamına gelmez. Kapatma içerisinden belirli bir yere geri dönmek , kontrol akışı açısından bir istisna atmak gibidir . Öyleyse kullanılan mekanizmanın bu olması şaşırtıcı değildir.
Rex Kerr

14
@RexKerr Peki, değdiğin için beni ikna ettin. Normalde, normal program akışı nedeniyle İstisnalara karşı bir demiryolu kurabilirdim, ancak iki ana neden burada geçerli değil. Bunlar: (1) yavaştırlar [bu şekilde kullanıldığında değil] ve (2) kodunuzu okuyan birine olağanüstü bir davranış önerirler [kütüphaneniz onları aramanıza izin vermiyorsa break] a benziyorsa breakve çalışırsa a. gibi break, endişelendiğim kadarıyla a break.
Tim Goodman

66

Mola kullanma mekanizması olan Scala 2.8'de bu durum değişti. Şimdi aşağıdakileri yapabilirsiniz:

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
Bu başlık altında istisnalar kullanıyor mu?
Mike

Bu, Scala'yı işlevsel programlama avantajlarını (yani kuyruk özyineleme) göz ardı eden yordamsal bir dil olarak kullanmaktadır. Güzel değil.
Galder Zamarreño

32
Mike: Evet, Scala döngüden çıkmak için bir istisna atıyor. Galder: Bu, "Scala'daki bir döngüden nasıl çıkabilirim?" 'Güzel' olup olmadığı önemli değildir.
hohonuuli

2
@hohonuuli, bu yüzden try-catch bloğunda kırılmaz, değil mi?
greenoldman

2
@Galder Zamarreño Bu durumda kuyruk özyineleme neden bir avantajdır? Bu sadece bir optimizasyon değil mi (kimin uygulaması yeni gelen için gizlidir ve deneyimli için kafa karıştırıcı bir şekilde uygulanır). Bu örnekte kuyruk özyinelemenin herhangi bir faydası var mı?
user48956

32

Bir for-loop'tan kurtulmak asla iyi bir fikir değildir. Bir for-loop kullanıyorsanız, kaç kez tekrarlamak istediğinizi bildiğiniz anlamına gelir. 2 koşullu bir while döngüsü kullanın.

Örneğin

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

2
Scala'da döngülerden kurtulmanın doğru yolu olduğunu düşünüyorum. Bu cevapta yanlış olan bir şey var mı? (düşük oy sayısı dikkate alındığında).
Jus12

1
gerçekten basit ve daha okunabilir. kırılabilir - kırılma şeyleri bile doğru, çirkin görünüyor ve iç deneme yakalamada problemleri var. Çözümünüz bir foreach ile çalışmıyor olsa da, basitliği onurlandırarak size oy vereceğim.
yerlilbilgin

13

Rex Kerr yanıtını başka bir şekilde eklemek için:

  • (1c) Ayrıca döngünüzde bir koruma kullanabilirsiniz:

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

30
Bunu bir seçenek olarak eklemedim çünkü aslında döngüyü kırmıyor - tümüyle çalışıyor, ancak toplam yeterince yüksek olduktan sonra if ifadesi başarısız oluyor, bu yüzden sadece bir if-ifadesinin her seferinde işe değer. Ne yazık ki, döngüyü nasıl yazdığınıza bağlı olarak, bu çok fazla iş olabilir.
Rex Kerr

@RexKerr: Derleyici zaten onu optimize etmeyecek mi? İlk çalıştırmada değilse JIT sırasında optimize edilmeyecektir.
Maciej Piechotka

5
@MaciejPiechotka - JIT derleyicisi genellikle değişen bir değişkene ilişkin if-ifadesinin her zaman (bu özel durumda) yanlış döndüreceğini ve dolayısıyla atlanabileceğini tanımak için yeterince gelişmiş mantık içermez.
Rex Kerr

6

breakScala'da henüz bulunmadığından , bu sorunu bir return-statement kullanarak çözmeye çalışabilirsiniz. Bu nedenle iç döngünüzü bir işleve koymanız gerekir, aksi takdirde dönüş tüm döngüyü atlar.

Scala 2.8, ancak kırmak için bir yol içeriyor

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


üzgünüm, ama sadece iç döngüyü kırmak istedim. Bir işleve koymam gerektiğini ima etmiyor musun?
Tianshuo

Üzgünüm, bunu netleştirmeliydim. Bir dönüş kullandığınızdan emin olun, döngüyü bir işlevde kapsüllemeniz gerekir. Cevabımı düzenledim.
Ham Vocke

1
Hiç hoş değil. Görünüşe göre Scala iç içe döngülerden hoşlanmıyor.
Tianshuo

Farklı bir yol yok gibi görünüyor. Şuna bir göz atmak isteyebilirsiniz: scala-lang.org/node/257
Ham Vocke

4
@TiansHUo: Neden Scala'nın iç içe döngülerden hoşlanmadığını söylüyorsun ? Tek bir döngüden çıkmaya çalışıyorsanız aynı sorunlarınız olur .
Rex Kerr


5

Bir while döngüsü kullanın:

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

5

Değerleri bir aralıkta yinelediğimiz gibi, bir kesme durumuna kadar, önce bir bütün aralık oluşturmak ve sonra bunun üzerinde tekrarlamak yerine Iterator, (@RexKerr kullanımı esinlenerek Stream)

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

Evet beğendim. kırılabilir bahane yok, bence daha güzel görünüyor.
ses

4

İşte bir kuyruk özyinelemeli sürüm. Anlayışlar ile karşılaştırıldığında, kuşkusuz, biraz şifreli, ancak işlevsel olduğunu söyleyebilirim :)

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

Gördüğünüz gibi, tr işlevi dış kavrayışların karşılığı ve içselin tr1'i. Sürümümü optimize etmenin bir yolunu biliyorsanız hoş geldiniz.


2

Çözümünüze yakın olacaksınız:

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-iterasyonu yeni bir kapsam olmadan yapılır ve ürün üretimi ve durum for-ifadede yapılır (iyi bir ifade değil - daha iyi bir tane bulamıyorum). Durum tersine çevrilir ve bu sorun boyutu için oldukça hızlıdır - belki daha büyük döngüler için bir mola ile bir şey kazanırsınız.

String.reverse dolaylı olarak RichString'e dönüştürür, bu yüzden 2 ekstra tersine çeviririm. :) Daha matematiksel bir yaklaşım daha zarif olabilir.


2

Üçüncü taraf breakablepaketi olası bir alternatiftir

https://github.com/erikerlandson/breakable

Örnek kod:

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 sınıfını kullanarak döngüyü kırmak için temel yöntem. Döngüyü kırılabilir olarak bildirerek.


2

Basitçe yapabiliriz scala olduğunu

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

çıktı :

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

1

İronik bir şekilde Scala zorla girme scala.util.control.Breaksbir istisnadır:

def break(): Nothing = { throw breakException }

En iyi tavsiye: mola, devam ve git KULLANMAYIN! IMO onlar aynı, kötü uygulama ve her türlü sorunun (ve sıcak tartışmaların) kötü bir kaynağıdır ve son olarak "zararlı olarak kabul edilir". Kod bloğu yapılandırılmıştır, ayrıca bu örnekte kesmeler gereksizdir. Edsger W.Dijkstra † yazdı:

Programcıların kalitesi, ürettikleri programlardaki ifadelere gitme yoğunluğunun azalan bir fonksiyonudur.


1

Aşağıdaki kod gibi bir durum var

 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 kullanıyorum ve mekanizması ctx.read hiçbir şey bulamadığında bir istisna atmak olmasıdır. Şu durumda tuzağa düştüm: Bir İstisna atıldığında döngüyü kırmak zorunda kaldım, ama loopla.util.control.Breaks.break döngüyü kırmak için İstisna kullanarak kırıldı ve bu yüzden yakalandı.

Bunu çözmek için çirkin bir yolum var: döngüyü ilk kez yapın ve gerçek uzunluğun sayısını alın. ve ikinci döngü için kullanın.

bazı java libs kullanırken Scala mola o kadar iyi değil.


1

Scala için yeniyim, ancak istisnalar atmaktan ve yöntemleri tekrar etmekten kaçının:

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

şöyle kullanın:

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

kırmak istemiyorsanız:

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

1

findToplama için akıllıca yöntem kullanımı sizin için hile yapacak.

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

Basit bir şekilde bir döngüyü kırmak için kod aşağıdadır

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 stilinin son 9 yılda ne kadar değiştiğini bilmiyorum, ancak mevcut cevapların çoğunun kullandığı varsya da okunması zor olan özyineleme buldum . Erken çıkmanın anahtarı, olası adaylarınızı oluşturmak için tembel bir koleksiyon kullanmak ve ardından durumu ayrı ayrı kontrol etmektir. Ürünleri üretmek için:

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

Ardından, her birleşimi oluşturmadan bu görünümdeki ilk palindromu bulmak için:

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

En büyük palindromu bulmak için (tembellik sizi çok fazla satın almasa da, listenin tamamını yine de kontrol etmeniz gerekiyor):

palindromes.max

Orijinal kodunuz aslında bir sonraki üründen daha büyük olan ilk palindromu kontrol ediyor. Ürünler kesinlikle monoton olarak azalmıyor. Örneğin, 998*998büyüktür 999*997, ancak döngülerde çok daha sonra görünür.

Her neyse, ayrı tembel nesil ve koşul kontrolünün avantajı, listenin tamamını kullanıyormuş gibi yazmanızdır, ancak yalnızca ihtiyacınız olduğu kadar üretir. Her iki dünyanın da en iyisini elde edersiniz.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.