스칼라에서 루프를 끊으려면 어떻게해야합니까?


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 루프를 꼬리 재귀로 어떻게 전환합니까?

FOSDEM 2009 http://www.slideshare.net/Odersky/fosdem-2009-1013261의 Scala Talk에서 22 페이지 :

스칼라는 휴식과 계속하지 않습니다. 왜? 그것들은 약간 필수적입니다. 더 많은 작은 기능을 더 잘 사용하십시오. 클로저와 상호 작용하는 방법을 발행하십시오. 그들은 필요하지 않습니다!

설명은 무엇입니까?


비교에는 if (product.toString == product.toString.reverse) 또는 등가 방법 호출과 같은 두 번째 등호가 필요합니다.
사용자가 알 수 없음

그래, 내가 그것을 입력 할 때 그 하나를 놓쳤다
TiansHUo

나는 오래된 질문을 부활시키고 있다는 것을 알고 있지만이 코드의 목적이 무엇인지 알고 싶습니다. 당신은 주어진의 조합으로 가능한 최대의 "회문"제품을 찾기 위해 노력하고 있었다 먼저 불구 i하고 j. 이 코드가 루프를 벗어나지 않고 완료된 경우 결과는 906609루프를 조기에 종료함으로써 결과가 90909루프를 벗어나게되면 결과가 변경 될 때 코드를 "더 효율적"으로 만들 수 없습니다.
Ryan H.

답변:


371

루프에서 벗어날 수있는 세 가지 옵션이 있습니다.

합계가 1000보다 클 때까지 숫자를 합산한다고 가정합니다.

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

때를 중지하려는 경우를 제외하고 (sum> 1000)

무엇을해야합니까? 몇 가지 옵션이 있습니다.

(1a) 테스트하는 조건을 포함하는 일부 구문을 사용하십시오.

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

(경고-이것은 평가하는 동안 takeWhile 테스트와 foreach가 어떻게 인터리브되는지에 대한 세부 사항에 달려 있으며, 실제로 사용해서는 안됩니다!).

(1b) 스칼라에서 새로운 메소드를 작성하는 것이 얼마나 쉬운 지 활용하여 for 루프 대신 테일 재귀를 사용하십시오.

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.BreaksC / 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

이것은 내가 생각할 수있는 적어도 세 가지 이유로 의도적으로 쉽지 않습니다. 첫째, 큰 코드 블록에서는 "continue"및 "break"문을 간과하거나, 실제보다 더 많거나 적은 것을 벗어나거나 할 수없는 두 개의 루프를 끊어야한다고 생각하기 쉽습니다. 어쨌든 쉽게-표준 사용법은 편리하지만 문제가 있으므로 코드를 다른 방식으로 구성해야합니다. 둘째, 스칼라에는 아마 눈치 채지 못하는 모든 종류의 중첩이 있습니다. 따라서 문제가 발생하면 코드 흐름이 끝나는 곳 (특히 폐쇄)에 놀랄 것입니다. 셋째, 스칼라의 "루프"대부분은 실제로 일반적인 루프가 아니며, 자체 루프가있는 메소드 호출입니다.루프와 유사하게, "break"등이 무엇을해야하는지 알 수있는 일관된 방법을 찾기는 어렵습니다. 따라서 일관성을 유지하기 위해 더 현명한 것은 "중단"하지 않는 것입니다.

참고 : 값을 sum변경하지 않고 값을 반환하는 모든 기능과 동등한 기능이 있습니다 . 이것들은 더 관용 스칼라입니다. 그러나 논리는 동일하게 유지됩니다. ( return해진다 return x등).


9
예외는 예외를 던질 수 있다는 것은 사실이지만, 예외 메커니즘을 남용하는 것입니다 (유효한 Java 참조). 예외는 실제로 예상치 못한 상황 및 / 또는 코드에서 급격한 탈출, 즉 어떤 종류의 오류가 필요한 상황을 나타내는 것입니다. 그 외에도 JVM이 최적화해야 할 이유가 거의 없기 때문에 확실히 현재 상황에 대해 잘 모르겠습니다.
Jonathan

28
@Jonathan-스택 추적을 계산해야하는 경우 예외가 느려집니다. 즉석에서 예외를 생성하는 대신 정적 예외를 생성하는 방법에 주목하십시오! 그리고 그것들은 완벽하게 유효한 제어 구조입니다. Scala 라이브러리 전체의 여러 곳에서 사용됩니다. 왜냐하면 실제로 여러 방법을 통해 돌아갈 수있는 유일한 방법이기 때문입니다.
렉스 커

18
@ Rex Kerr, 당신은 break 구문의 약점을 지적하고 있지만 (나는 동의하지 않습니다) 정상적인 워크 플로우에 예외 를 사용하는 것이 좋습니다 ! 루프를 종료하는 것은 예외가 아니며 알고리즘의 일부이며 존재하지 않는 파일에 쓰는 경우는 아닙니다 (예 :). 간단히 말해 "치료"는 "질병"자체보다 나쁩니다. 그리고 breakable섹션 에서 실제 예외를 던지는 것을 고려할 때 ... 악을 피하기 위해 모든 농구 대 break, 흠 ;-) 인정해야합니다, 인생은 아이러니합니다.
greenoldman

17
@macias-죄송합니다, 제 실수입니다. JVM이 제어 플로우에 Throwable을 사용하고 있습니다. 보다 나은? 일반적으로 예외 처리를 지원하는 데 사용된다고해서 예외 처리에만 사용할 수있는 것은 아닙니다. 클로저 내에서 정의 된 위치로 돌아가는 것은 제어 흐름 측면에서 예외를 던지는 것과 같습니다 . 그러므로 이것이 사용되는 메커니즘이라는 것은 놀라운 일이 아닙니다.
렉스 커

14
@RexKerr 글쎄, 당신이 저를 설득 할 가치가있는 것에 대해. 일반적으로 정상적인 프로그램 흐름에 대한 예외를 피할 수는 있지만 두 가지 주요 이유는 여기에 적용되지 않습니다. 사람들은 다음과 같습니다 (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
이것은 후드에서 예외를 사용합니까?
Mike

이는 기능적 프로그래밍 이점 (예 : 꼬리 재귀)을 무시하고 절차 언어로 스칼라를 사용합니다. 예쁘지 않은.
Galder Zamarreño

32
마이크 : 네, 스칼라는 루프에서 벗어나기 위해 예외를 던지고 있습니다. Galder : 이것은 게시 된 질문 "스칼라에서 루프를 어떻게 끊습니까?" 그것이 '꽤'인지 아닌지는 관련이 없습니다.
hohonuuli

2
@hohonuuli, try-catch 블록에 있으므로 깨지지 않습니다.
greenoldman

2
@Galder Zamarreño 왜이 경우 꼬리 재귀가 유리합니까? 단순히 최적화가 아닌가 (누구의 응용 프로그램은 신참자를 위해 숨겨져 있으며 경험이 많은 사람들에게는 혼란스럽게 적용됩니다). 이 예에서 꼬리 재귀에 대한 이점이 있습니까?
user48956

32

for-loop를 벗어나는 것은 좋은 생각이 아닙니다. for-loop를 사용하는 경우 반복하려는 횟수를 알고 있음을 의미합니다. 2 가지 조건의 while 루프를 사용하십시오.

예를 들어

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

2
이것이 내가 스칼라의 루프에서 벗어날 수있는 올바른 방법이라고 생각합니다. 이 답변에 문제가 있습니까? (공백 수가 적음).
Jus12

1
실제로 간단하고 읽기 쉽습니다. 깨지기 쉬운 것조차-깨지는 것은 정확하고, 추악하게 보이고 내부 try-catch에 문제가 있습니다. 귀하의 솔루션은 foreach와 함께 작동하지 않지만 단순성을 존중하여 투표합니다.
yerlilbilgin

13

다른 방법으로 렉스 커를 추가하려면 :

  • (1c) 루프에서 가드를 사용할 수도 있습니다.

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

30
루프를 실제로 중단하지 않기 때문에 옵션으로 포함하지 않았습니다. 모든 루프를 통과하지만 if 문이 충분히 높은 후에 모든 반복에서 if 문이 실패하므로 if 문 하나만 수행합니다. 매번 일할 가치가 있습니다. 불행히도 루프를 작성한 방법에 따라 많은 작업이 필요할 수 있습니다.
렉스 커

@ RexKerr : 컴파일러가 어쨌든 최적화하지 않습니까? JIT 중에 처음 실행하는 동안이 아니라면 최적화되지 않습니다.
Maciej Piechotka

5
@MaciejPiechotka-JIT 컴파일러에는 일반적으로 변경 변수에 대한 if 문이 항상 (이 특별한 상황에서) false를 리턴하므로 생략 할 수 있음을 인식하기에 충분히 정교한 로직이 포함되어 있지 않습니다.
Rex Kerr

6

break스칼라에는 아직 없기 때문에-문 을 사용 하여이 문제를 해결할 수 return있습니다. 따라서 내부 루프를 함수에 넣어야합니다. 그렇지 않으면 리턴이 전체 루프를 건너 뜁니다.

스칼라 2.8은 그러나 깨는 방법을 포함

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


죄송하지만 내부 루프 만 끊고 싶었습니다. 함수에 넣어야한다는 것을 암시하지 않습니까?
TiansHUo

죄송합니다. 리턴을 사용한다는 것은 함수에서 루프를 캡슐화해야한다는 것을 의미합니다. 내 답변을 편집했습니다.
Ham Vocke

1
전혀 좋지 않습니다. 스칼라는 중첩 루프를 좋아하지 않는 것 같습니다.
TiansHUo

다른 방법은없는 것 같습니다. 당신은 이것 좀 걸릴 수도 있습니다 : scala-lang.org/node/257을
햄 Vocke

4
@TiansHUo : 왜 스칼라는 중첩 루프를 좋아하지 않는다고 말 합니까? 단일 루프에서 벗어나려고 할 때도 동일한 문제가 있습니다 .
렉스 커


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

그래, 난 좋아. 변명 할 수없는 실례가 아니라고 생각합니다.
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-iteration은 새로운 범위없이 이루어지며 제품 생성 및 조건은 for-statement에서 수행됩니다 (좋은 표현이 아닙니다-더 나은 것을 찾지 못합니다). 조건은 그 문제의 크기에 대해 매우 빠르며 반전됩니다. 아마도 더 큰 루프에서 휴식을 취하는 무언가를 얻을 수 있습니다.

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 }

가장 좋은 조언은 : 휴식을 취하지 말고 계속 진행하십시오! IMO는 모든 종류의 문제 (그리고 뜨거운 토론)의 동일하고 나쁜 관행이며 악의적 근원이며 마지막으로 "유해한 것으로 간주됩니다". 이 예제에서도 코드 블록 구조가 불필요합니다. 우리의 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가 아무것도 찾을 수 없을 때 예외를 throw한다는 것입니다. 나는 예외가 발생했을 때 루프를 끊어야하지만 scala.util.control.Breaks.break는 예외를 사용하여 루프를 끊고 catch 블록에 있었기 때문에 잡혔습니다.

나는 이것을 해결하기위한 추한 방법을 얻었다 : 처음으로 루프를 수행하고 실제 길이의 수를 얻는다. 두 번째 루프에 사용하십시오.

자바 라이브러리를 사용할 때 스칼라에서 휴식을 취하는 것은 그리 좋지 않습니다.


1

나는 스칼라를 처음 사용하지만 예외가 발생하고 반복되는 방법을 피하기 위해 어떻습니까?

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 년 동안 스칼라 스타일이 얼마나 많이 바뀌 었는지 모르겠지만, 기존 답변의 대부분이를 사용 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.