Làm thế nào để tôi thoát ra khỏi một vòng lặp trong Scala?


276

Làm thế nào để tôi thoát ra một vòng lặp?

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

Làm thế nào để tôi biến các vòng lặp lồng nhau thành đệ quy đuôi?

Từ Scala Talk tại FOSDEM 2009 http://www.sl slideshoware.net/Odersky/fosdem-2009-1013261 trên trang 22:

Phá vỡ và tiếp tục Scala không có chúng. Tại sao? Họ là một chút cấp bách; sử dụng tốt hơn nhiều chức năng nhỏ hơn Vấn đề làm thế nào để tương tác với các bao đóng. Họ không cần thiết!

Giải thích là gì?


Sự so sánh của bạn cần một dấu bằng thứ hai: if (sản phẩm.toString == sản phẩm.toString.reverse) hoặc có thể là một cuộc gọi bằng phương thức.
người dùng không xác định

vâng, tôi đã bỏ lỡ cái đó khi tôi gõ nó vào
TiansHUo

Tôi biết tôi đang hồi sinh một câu hỏi cũ nhưng tôi rất muốn biết mục đích của mã này là gì? Trước tiên tôi nghĩ rằng bạn đang cố gắng tìm sản phẩm "palindrom" lớn nhất có thể với các kết hợp ij. Nếu mã này chạy đến hoàn thành mà không thoát ra khỏi vòng lặp, kết quả là 906609nhưng bằng cách thoát ra khỏi vòng lặp sớm, kết quả là 90909việc thoát ra khỏi vòng lặp sẽ không làm cho mã "hiệu quả hơn" vì nó làm thay đổi kết quả.
Ryan H.

Câu trả lời:


371

Bạn có ba tùy chọn (hoặc hơn) để thoát ra khỏi các vòng lặp.

Giả sử bạn muốn tính tổng số cho đến khi tổng lớn hơn 1000. Bạn thử

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

ngoại trừ bạn muốn dừng khi (tổng> 1000).

Phải làm sao? Có một số lựa chọn.

(1a) Sử dụng một số cấu trúc bao gồm một điều kiện mà bạn kiểm tra.

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

(cảnh báo - điều này phụ thuộc vào chi tiết về cách kiểm tra TakeWhile và foreach được xen kẽ trong quá trình đánh giá và có lẽ không nên được sử dụng trong thực tế!).

(1b) Sử dụng đệ quy đuôi thay vì vòng lặp for, tận dụng việc viết phương thức mới trong Scala dễ dàng như thế nào:

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

(1c) Quay trở lại sử dụng vòng lặp while

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

(2) Ném một ngoại lệ.

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

(2a) Trong Scala 2.8+, phần này đã được đóng gói sẵn scala.util.control.Breaksbằng cách sử dụng cú pháp trông rất giống với dấu ngắt cũ quen thuộc của bạn từ C / Java:

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

(3) Đặt mã vào một phương thức và sử dụng trả về.

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

Điều này được cố ý thực hiện không quá dễ dàng vì ít nhất ba lý do tôi có thể nghĩ ra. Đầu tiên, trong các khối mã lớn, thật dễ dàng bỏ qua các câu lệnh "tiếp tục" và "phá vỡ" hoặc nghĩ rằng bạn đang thoát ra khỏi nhiều hơn hoặc ít hơn bạn thực sự hoặc cần phải phá vỡ hai vòng lặp mà bạn không thể làm dù sao cũng dễ dàng - vì vậy việc sử dụng tiêu chuẩn, trong khi tiện dụng, có vấn đề của nó, và do đó bạn nên cố gắng cấu trúc mã của mình theo một cách khác. Thứ hai, Scala có tất cả các loại tổ mà bạn có thể thậm chí không nhận thấy, vì vậy nếu bạn có thể thoát ra khỏi mọi thứ, có lẽ bạn sẽ ngạc nhiên về nơi dòng mã kết thúc (đặc biệt là với các lần đóng). Thứ ba, hầu hết các "vòng lặp" của Scala không thực sự là các vòng lặp thông thường - chúng là các cuộc gọi phương thức có vòng lặp riêng,giống như vòng lặp, thật khó để đưa ra một cách nhất quán để biết "phá vỡ" và những điều tương tự nên làm. Vì vậy, để nhất quán, điều khôn ngoan hơn cần làm là không có một "phá vỡ" nào cả.

Lưu ý : Có các chức năng tương đương của tất cả những điều này khi bạn trả về giá trị sumthay vì thay đổi nó tại chỗ. Đây là nhiều Scala thành ngữ. Tuy nhiên, logic vẫn giữ nguyên. ( returntrở thành return x, v.v.).


9
Mặc dù các trường hợp ngoại lệ, mặc dù đúng là bạn có thể đưa ra một ngoại lệ, đây có thể là một sự lạm dụng của cơ chế ngoại lệ (xem Java hiệu quả). Các trường hợp ngoại lệ thực sự là để chỉ ra các tình huống thực sự bất ngờ và / hoặc yêu cầu thoát mạnh khỏi mã, tức là lỗi thuộc loại nào đó. Ngoài ra, họ chắc chắn đã sử dụng khá chậm (không chắc chắn về tình hình hiện tại) bởi vì có rất ít lý do để các JVM tối ưu hóa chúng.
Jonathan

28
@Jonathan - Các ngoại lệ chỉ chậm nếu bạn cần tính toán dấu vết ngăn xếp - chú ý cách tôi tạo một ngoại lệ tĩnh để ném thay vì tạo một dấu vết khi đang di chuyển! Và chúng là một cấu trúc điều khiển hoàn toàn hợp lệ; chúng được sử dụng ở nhiều nơi trong thư viện Scala, vì chúng thực sự là cách duy nhất bạn có thể quay lại qua nhiều phương thức (mà nếu bạn có một đống đóng là điều đôi khi bạn cần phải làm).
Rex Kerr

18
@Rex Kerr, bạn đang chỉ ra những điểm yếu của cấu trúc phá vỡ (tôi không đồng ý với chúng), nhưng sau đó bạn đề xuất sử dụng ngoại lệ cho quy trình làm việc bình thường! Thoát khỏi một vòng lặp không phải là một trường hợp ngoại lệ, nó là một phần của thuật toán, nó không phải là trường hợp ghi vào tệp không tồn tại (ví dụ). Vì vậy, trong ngắn hạn đề xuất "chữa bệnh" còn tồi tệ hơn chính "căn bệnh". Và khi tôi cân nhắc việc ném một ngoại lệ thực sự trong breakablephần ... và tất cả những hoops đó chỉ để tránh điều ác break, hmm ;-) Bạn phải thừa nhận, cuộc sống thật mỉa mai.
greenoldman

17
@macias - Xin lỗi, lỗi của tôi. JVM đang sử dụng throwables cho luồng điều khiển. Tốt hơn? Chỉ vì chúng thường được sử dụng để hỗ trợ xử lý ngoại lệ không có nghĩa là chúng chỉ có thể được sử dụng để xử lý ngoại lệ. Quay trở lại một vị trí được xác định từ trong một bao đóng cũng giống như ném một ngoại lệ về lưu lượng kiểm soát. Sau đó, không có gì ngạc nhiên khi đây là cơ chế được sử dụng.
Rex Kerr

14
@RexKerr Vâng, vì những gì nó đáng để bạn thuyết phục tôi. Thông thường tôi sẽ là người chống lại Ngoại lệ cho luồng chương trình bình thường, nhưng hai lý do chính không được áp dụng ở đây. Đó là: (1) chúng chậm [không được sử dụng theo cách này] và (2) chúng đề xuất hành vi đặc biệt cho ai đó đọc mã của bạn [không phải nếu thư viện của bạn cho phép bạn gọi cho họ break] Nếu nó trông giống như một breakvà nó thực hiện như một break, theo như tôi quan tâm đó là a break.
Tim Goodman

66

Điều này đã thay đổi trong Scala 2.8 có cơ chế sử dụng ngắt. Bây giờ bạn có thể làm như sau:

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
Điều này có sử dụng ngoại lệ dưới mui xe?
Mike

Điều này đang sử dụng Scala như một ngôn ngữ thủ tục bỏ qua các lợi thế lập trình chức năng (tức là đệ quy đuôi). Không đẹp.
Galder Zamarreño

32
Mike: Vâng, Scala đang ném một ngoại lệ để thoát ra khỏi vòng lặp. Galder: Điều này trả lời câu hỏi được đăng "Làm thế nào để tôi thoát ra khỏi một vòng lặp trong Scala?". Cho dù nó "đẹp" hay không không liên quan.
hohonuuli

2
@hohonuuli, vậy là trong khối thử bắt nó sẽ không bị hỏng, phải không?
greenoldman

2
@Galder Zamarreño Tại sao đệ quy đuôi là một lợi thế trong trường hợp này? Không phải nó chỉ đơn giản là một sự tối ưu hóa (ứng dụng của ai bị ẩn cho người mới và áp dụng một cách khó hiểu cho người có kinh nghiệm). Có bất kỳ lợi ích cho đệ quy đuôi trong ví dụ này?
dùng48956

32

Nó không bao giờ là một ý tưởng tốt để thoát ra khỏi một vòng lặp for. Nếu bạn đang sử dụng vòng lặp for, điều đó có nghĩa là bạn biết bạn muốn lặp lại bao nhiêu lần. Sử dụng vòng lặp while với 2 điều kiện.

ví dụ

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

2
Đây là những gì tôi cảm thấy là cách đúng đắn để thoát ra khỏi các vòng lặp trong Scala. Có điều gì sai với câu trả lời này? (xem xét số lượng upvote thấp).
Jus12

1
thực sự đơn giản và dễ đọc hơn ngay cả sự dễ vỡ - phá vỡ điều là chính xác, nó trông xấu xí và có vấn đề với việc thử bắt bên trong. Mặc dù giải pháp của bạn không hoạt động với một bài giảng, tôi sẽ bỏ phiếu cho bạn, tôn vinh sự đơn giản.
yerlilbilgin

13

Để thêm Rex Kerr trả lời theo cách khác:

  • (1c) Bạn cũng có thể sử dụng một người bảo vệ trong vòng lặp của mình:

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

30
Tôi đã không bao gồm điều này như là một tùy chọn vì nó không thực sự phá vỡ vòng lặp - nó chạy qua tất cả, nhưng câu lệnh if thất bại trên mỗi lần lặp sau khi tổng tiền đủ cao, vì vậy nó chỉ thực hiện một câu lệnh if giá trị của công việc mỗi lần. Thật không may, tùy thuộc vào cách bạn viết vòng lặp, đó có thể là rất nhiều công việc.
Rex Kerr

@RexKerr: Trình biên dịch sẽ không tối ưu hóa nó chứ? Sẽ không được tối ưu hóa nếu không trong lần chạy đầu tiên sau đó trong JIT.
Maciej Piechotka

5
@MaciejPiechotka - Trình biên dịch JIT thường không chứa logic đủ phức tạp để nhận ra rằng một câu lệnh if trên một biến thay đổi sẽ luôn luôn (trong tình huống đặc biệt này) trả về sai và do đó có thể bị bỏ qua.
Rex Kerr

6

Vì chưa có breaktrong Scala, bạn có thể cố gắng giải quyết vấn đề này bằng cách sử dụng phân tầng return. Do đó, bạn cần đặt vòng lặp bên trong của mình vào một hàm, nếu không thì trả về sẽ bỏ qua toàn bộ vòng lặp.

Scala 2.8 tuy nhiên bao gồm một cách để phá vỡ

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


xin lỗi, nhưng tôi chỉ muốn thoát ra khỏi vòng lặp bên trong Bạn không ngụ ý rằng tôi nên đặt nó trong một chức năng?
TiansHUo

Xin lỗi, nên đã làm rõ điều đó. Chắc chắn sử dụng trả về có nghĩa là bạn cần đóng gói vòng lặp trong một hàm. Tôi đã chỉnh sửa câu trả lời của mình.
Ham Vocke

1
Điều đó không tốt chút nào. Có vẻ như Scala không thích các vòng lặp lồng nhau.
TiansHUo

Dường như không có một cách khác. Bạn có thể muốn xem cái này: scala-lang.org/node/257
Ham

4
@TiansHUo: Tại sao bạn nói rằng Scala không thích các vòng lặp lồng nhau ? Bạn có những vấn đề tương tự nếu bạn đang cố gắng thoát ra khỏi một vòng lặp duy nhất .
Rex Kerr


5

Chỉ cần sử dụng một vòng lặp while:

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

5

Một cách tiếp cận tạo ra các giá trị trong một phạm vi khi chúng ta lặp lại, đến một điều kiện phá vỡ, thay vì tạo ra toàn bộ phạm vi đầu tiên và sau đó lặp lại nó, sử dụng Iterator, (lấy cảm hứng từ việc sử dụng @RexKerr Stream)

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

Vâng, tôi thích nó. không có lý do phá vỡ, tôi nghĩ rằng nó trông đẹp hơn.

4

Đây là một phiên bản đệ quy đuôi. So với những gì để hiểu, nó hơi khó hiểu, nhưng tôi muốn nói rằng nó có chức năng :)

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

Như bạn có thể thấy, hàm tr là đối tác của sự hiểu biết bên ngoài và tr1 của hàm bên trong. Bạn được chào đón nếu bạn biết một cách để tối ưu hóa phiên bản của tôi.


2

Gần với giải pháp của bạn sẽ là thế này:

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)

Lặp đi lặp lại được thực hiện mà không có phạm vi mới và việc tạo sản phẩm cũng như điều kiện được thực hiện trong câu lệnh for (không phải là biểu thức tốt - tôi không tìm thấy cách nào tốt hơn). Điều kiện được đảo ngược, điều này khá nhanh đối với kích thước vấn đề đó - có thể bạn đạt được điều gì đó với một khoảng nghỉ cho các vòng lặp lớn hơn.

String.reverse hoàn toàn chuyển đổi thành RichString, đó là lý do tại sao tôi thực hiện thêm 2 lần đảo ngược. :) Một cách tiếp cận toán học hơn có thể thanh lịch hơn.


2

Gói của bên thứ ba breakablelà một sự thay thế có thể

https://github.com/erikerlandson / breakable

Mã ví dụ:

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

Phương thức cơ bản để phá vỡ vòng lặp, sử dụng lớp Breaks. Bằng cách khai báo vòng lặp là có thể phá vỡ.


2

Đơn giản chúng ta có thể làm trong scala là

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

đầu ra:

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

1

Trớ trêu thay, Scala đột nhập scala.util.control.Breakslà một ngoại lệ:

def break(): Nothing = { throw breakException }

Lời khuyên tốt nhất là: KHÔNG sử dụng break, tiếp tục và goto! IMO họ giống nhau, thực tiễn xấu và là nguồn gốc của tất cả các loại vấn đề (và các cuộc thảo luận nóng) và cuối cùng "được coi là có hại". Khối mã có cấu trúc, cũng trong ví dụ này phá vỡ là thừa. Edsger W. Dijkstra của chúng tôi đã viết:

Chất lượng của các lập trình viên là một hàm giảm của mật độ đi đến các câu lệnh trong các chương trình họ sản xuất.


1

Tôi có một tình huống như mã dưới đây

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

Tôi đang sử dụng java lib và cơ chế là ctx.read ném Ngoại lệ khi nó không tìm thấy gì. Tôi đã bị mắc kẹt trong tình huống: Tôi phải phá vỡ vòng lặp khi Exception bị ném, nhưng scala.util.control.Breaks.break sử dụng Exception để phá vỡ vòng lặp và nó nằm trong khối bắt do đó nó bị bắt.

Tôi có cách xấu để giải quyết điều này: lần đầu tiên thực hiện vòng lặp và đếm số chiều dài thực. và sử dụng nó cho vòng lặp thứ hai.

nghỉ ra khỏi Scala là không tốt, khi bạn đang sử dụng một số java libs.


1

Tôi chưa quen với Scala, nhưng làm thế nào để tránh ném ngoại lệ và lặp lại các phương pháp:

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

sử dụng nó như thế này:

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

nếu bạn không muốn phá vỡ:

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

1

Khéo léo sử dụng findphương pháp cho bộ sưu tập sẽ làm cho bạn mẹo.

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

Dưới đây là mã để phá vỡ một vòng lặp một cách đơn giản

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

Tôi không biết phong cách Scala đã thay đổi bao nhiêu trong 9 năm qua, nhưng tôi thấy điều thú vị là hầu hết các câu trả lời hiện có đều sử dụng varshoặc khó đọc đệ quy. Chìa khóa để thoát sớm là sử dụng bộ sưu tập lười biếng để tạo ra các ứng cử viên có thể của bạn, sau đó kiểm tra điều kiện riêng. Để tạo ra các sản phẩm:

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

Sau đó, để tìm bảng màu đầu tiên từ chế độ xem đó mà không tạo mọi kết hợp:

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

Để tìm bảng màu lớn nhất (mặc dù sự lười biếng không mua cho bạn nhiều vì dù sao bạn cũng phải kiểm tra toàn bộ danh sách):

palindromes.max

Mã ban đầu của bạn thực sự đang kiểm tra palindrom đầu tiên lớn hơn sản phẩm tiếp theo, giống như kiểm tra palindrom đầu tiên ngoại trừ trong điều kiện ranh giới kỳ lạ mà tôi không nghĩ là bạn dự định. Các sản phẩm không hoàn toàn giảm đơn điệu. Ví dụ, 998*998lớn hơn 999*997, nhưng xuất hiện nhiều hơn trong các vòng lặp.

Dù sao, lợi thế của thế hệ lười biếng tách biệt và kiểm tra điều kiện là bạn viết nó khá giống như nó đang sử dụng toàn bộ danh sách, nhưng nó chỉ tạo ra nhiều như bạn cần. Bạn sắp xếp tốt nhất của cả hai thế giới.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.