접기 초기에 중단
접기를 일찍 종료하는 가장 좋은 방법은 무엇입니까? 단순화 된 예를 들어,의 숫자를 합산하고 Iterable
싶지만 예상치 못한 (홀수라고 말하면) 무언가를 만나면 종료하고 싶을 수 있습니다. 이것은 첫 번째 근사치입니다.
def sumEvenNumbers(nums: Iterable[Int]): Option[Int] = {
nums.foldLeft (Some(0): Option[Int]) {
case (Some(s), n) if n % 2 == 0 => Some(s + n)
case _ => None
}
}
그러나이 솔루션은 매우 추하고 (.foreach 및 return을 수행하면 훨씬 깨끗하고 명확합니다) 최악의 경우 짝수가 아닌 숫자를 만나더라도 전체 반복 가능을 순회합니다. .
그렇다면 이와 같이 폴드를 작성하는 가장 좋은 방법은 무엇입니까? 그냥 가서 이것을 재귀 적으로 써야합니까, 아니면 더 받아 들여지는 방법이 있습니까?
내 첫 번째 선택은 일반적으로 재귀를 사용하는 것입니다. 다소 덜 컴팩트하고 잠재적으로 더 빠르며 (확실히 더 느리지 않음) 조기 종료시 로직을 더 명확하게 만들 수 있습니다. 이 경우 약간 어색한 중첩 정의가 필요합니다.
def sumEvenNumbers(nums: Iterable[Int]) = {
def sumEven(it: Iterator[Int], n: Int): Option[Int] = {
if (it.hasNext) {
val x = it.next
if ((x % 2) == 0) sumEven(it, n+x) else None
}
else Some(n)
}
sumEven(nums.iterator, 0)
}
두 번째 선택은를 사용 return
하는 것입니다. 다른 모든 것을 그대로 유지하고 접기 만하여 def
반환 할 항목을 가지기 만하면됩니다.이 경우에는 이미 메서드가 있습니다.
def sumEvenNumbers(nums: Iterable[Int]): Option[Int] = {
Some(nums.foldLeft(0){ (n,x) =>
if ((n % 2) != 0) return None
n+x
})
}
이 특별한 경우에는 재귀보다 훨씬 더 간결합니다 (우리는 반복 / 반복 변환을 수행해야했기 때문에 재귀에 특히 불행했습니다). 비정상적인 제어 흐름은 다른 모든 것이 동일 할 때 피해야 할 것이지만 여기서는 그렇지 않습니다. 귀중한 경우 사용해도 해가 없습니다.
이 작업을 자주 수행하고 어딘가에있는 메서드 중간에 원하는 경우 (반환 만 사용할 수는 없음) 예외 처리를 사용하여 로컬이 아닌 제어 흐름을 생성 할 수 있습니다. 즉, 결국 그것이 좋은 점이며 오류 처리가 유용한 유일한 시간은 아닙니다. 유일한 트릭은 스택 트레이스 (정말 느린 속도)를 생성 하지 않는 것입니다. 트레이 트 NoStackTrace
와 그 자식 트레이 트가 ControlThrowable
이미 그렇게 하기 때문에 쉽습니다 . Scala는 이미 이것을 내부적으로 사용하고 있습니다 (사실, 이것이 폴드 내부에서 리턴을 구현하는 방법입니다!). 우리 자신을 만들어 봅시다 (중첩 될 수는 없지만 수정할 수 있습니다) :
import scala.util.control.ControlThrowable
case class Returned[A](value: A) extends ControlThrowable {}
def shortcut[A](a: => A) = try { a } catch { case Returned(v) => v }
def sumEvenNumbers(nums: Iterable[Int]) = shortcut{
Option(nums.foldLeft(0){ (n,x) =>
if ((x % 2) != 0) throw Returned(None)
n+x
})
}
물론 여기서 사용하는 return
것이 더 좋지만 shortcut
전체 메서드를 래핑하는 것이 아니라 어디에나 넣을 수 있습니다.
저에게 다음 줄은 fold를 다시 구현하는 것입니다 (나 자신이나 그것을 수행하는 라이브러리를 찾기 위해) 조기 종료 신호를 보낼 수 있습니다. 이를 수행하는 두 가지 자연스러운 방법은 값을 전파하지 않고 값을 Option
포함하는 것입니다. 여기서 None
종료를 의미합니다. 또는 완료를 알리는 두 번째 표시기 기능을 사용합니다. Kim Stebel이 보여준 Scalaz lazy fold는 이미 첫 번째 경우를 다루고 있으므로 두 번째 사례를 보여 드리겠습니다 (변경 가능한 구현 포함).
def foldOrFail[A,B](it: Iterable[A])(zero: B)(fail: A => Boolean)(f: (B,A) => B): Option[B] = {
val ii = it.iterator
var b = zero
while (ii.hasNext) {
val x = ii.next
if (fail(x)) return None
b = f(b,x)
}
Some(b)
}
def sumEvenNumbers(nums: Iterable[Int]) = foldOrFail(nums)(0)(_ % 2 != 0)(_ + _)
(재귀, 반환, 게으름 등으로 종료를 구현할지 여부는 귀하에게 달려 있습니다.)
나는 그것이 주요 합리적인 변형을 포함한다고 생각합니다. 다른 옵션도 있지만이 경우 왜 사용하는지 모르겠습니다. ( Iterator
가있는 경우 자체적으로 잘 작동 findOrPrevious
하지만 그렇지 않은 경우 손으로 작업하는 데 추가 작업이 필요하므로 여기서 사용하는 것은 어리석은 옵션이됩니다.)
설명하는 시나리오 (원치 않는 조건에서 종료)는 takeWhile
방법에 대한 좋은 사용 사례처럼 보입니다 . 본질적 filter
으로이지만 조건을 충족하지 않는 요소를 만나면 종료되어야합니다.
예를 들면 :
val list = List(2,4,6,8,6,4,2,5,3,2)
list.takeWhile(_ % 2 == 0) //result is List(2,4,6,8,6,4,2)
이것은 Iterator
s / Iterable
s에서도 잘 작동 합니다. "짝수 합계이지만 홀수로 나누기"에 대해 제안하는 해결책은 다음과 같습니다.
list.iterator.takeWhile(_ % 2 == 0).foldLeft(...)
홀수에 도달하면 시간을 낭비하지 않는다는 것을 증명하기 위해 ...
scala> val list = List(2,4,5,6,8)
list: List[Int] = List(2, 4, 5, 6, 8)
scala> def condition(i: Int) = {
| println("processing " + i)
| i % 2 == 0
| }
condition: (i: Int)Boolean
scala> list.iterator.takeWhile(condition _).sum
processing 2
processing 4
processing 5
res4: Int = 6
scalaz에서 lazy 버전의 foldRight를 사용하여 기능적인 스타일로 원하는 것을 할 수 있습니다. 자세한 설명은 이 블로그 게시물을 참조하십시오 . 이 솔루션은 사용하는 동안 Stream
, 당신은 변환 할 수 있습니다 Iterable
에 Stream
효율적으로 iterable.toStream
.
import scalaz._
import Scalaz._
val str = Stream(2,1,2,2,2,2,2,2,2)
var i = 0 //only here for testing
val r = str.foldr(Some(0):Option[Int])((n,s) => {
println(i)
i+=1
if (n % 2 == 0) s.map(n+) else None
})
이것은 인쇄 만
0
1
익명 함수가 두 번만 호출된다는 것을 분명히 보여줍니다 (즉, 홀수를 만날 때까지). 이는 서명 (의 경우 Stream
)이 인 foldr의 정의 때문 입니다 def foldr[B](b: B)(f: (Int, => B) => B)(implicit r: scalaz.Foldable[Stream]): B
. 익명 함수는 이름 매개 변수를 두 번째 인수로 사용하므로 평가할 필요가 없습니다.
Btw, OP의 패턴 매칭 솔루션으로 여전히 이것을 작성할 수 있지만 if / else를 찾고 더 우아하게 매핑합니다.
Well, Scala does allow non local returns. There are differing opinions on whether or not this is a good style.
scala> def sumEvenNumbers(nums: Iterable[Int]): Option[Int] = {
| nums.foldLeft (Some(0): Option[Int]) {
| case (None, _) => return None
| case (Some(s), n) if n % 2 == 0 => Some(s + n)
| case (Some(_), _) => None
| }
| }
sumEvenNumbers: (nums: Iterable[Int])Option[Int]
scala> sumEvenNumbers(2 to 10)
res8: Option[Int] = None
scala> sumEvenNumbers(2 to 10 by 2)
res9: Option[Int] = Some(30)
EDIT:
In this particular case, as @Arjan suggested, you can also do:
def sumEvenNumbers(nums: Iterable[Int]): Option[Int] = {
nums.foldLeft (Some(0): Option[Int]) {
case (Some(s), n) if n % 2 == 0 => Some(s + n)
case _ => return None
}
}
Cats has a method called foldM which does short-circuiting (for Vector
, List
, Stream
, ...).
It works as follows:
def sumEvenNumbers(nums: Stream[Int]): Option[Long] = {
import cats.implicits._
nums.foldM(0L) {
case (acc, c) if c % 2 == 0 => Some(acc + c)
case _ => None
}
}
As soon as one of the elements on the collection is not even, it returns.
@Rex Kerr your answer helped me, but I needed to tweak it to use Either
def foldOrFail[A,B,C,D](map: B => Either[D, C])(merge: (A, C) => A)(initial: A)(it: Iterable[B]): Either[D, A] = { val ii= it.iterator var b= initial while (ii.hasNext) { val x= ii.next map(x) match { case Left(error) => return Left(error) case Right(d) => b= merge(b, d) } } Right(b) }
You could try using a temporary var and using takeWhile. Here is a version.
var continue = true
// sample stream of 2's and then a stream of 3's.
val evenSum = (Stream.fill(10)(2) ++ Stream.fill(10)(3)).takeWhile(_ => continue)
.foldLeft(Option[Int](0)){
case (result,i) if i%2 != 0 =>
continue = false;
// return whatever is appropriate either the accumulated sum or None.
result
case (optionSum,i) => optionSum.map( _ + i)
}
The evenSum
should be Some(20)
in this case.
You can throw a well-chosen exception upon encountering your termination criterion, handling it in the calling code.
A more beutiful solution would be using span:
val (l, r) = numbers.span(_ % 2 == 0)
if(r.isEmpty) Some(l.sum)
else None
... but it traverses the list two times if all the numbers are even
Just for an "academic" reasons (:
var headers = Source.fromFile(file).getLines().next().split(",")
var closeHeaderIdx = headers.takeWhile { s => !"Close".equals(s) }.foldLeft(0)((i, S) => i+1)
Takes twice then it should but it is a nice one liner. If "Close" not found it will return
headers.size
Another (better) is this one:
var headers = Source.fromFile(file).getLines().next().split(",").toList
var closeHeaderIdx = headers.indexOf("Close")
참고URL : https://stackoverflow.com/questions/12892701/abort-early-in-a-fold
'your programing' 카테고리의 다른 글
모바일 Google 크롬은 브라우저 확장을 지원합니까? (0) | 2020.10.10 |
---|---|
Python을 사용하여 iOS 및 Android 용 크로스 플랫폼 앱을 작성할 수 있습니까? (0) | 2020.10.10 |
IHttpHandler 대 IHttpModule (0) | 2020.10.10 |
여러 활동에서 Android 애플리케이션을 어떻게 테스트합니까? (0) | 2020.10.10 |
로그인에서 요청한 데이터베이스 "테스트"를 열 수 없습니다. (0) | 2020.10.10 |