中止提前

什么是尽早终止弃牌的最好方法? 作为一个简单的例子,想象一下我想总结一个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 } } 

然而,这个解决scheme是相当丑陋的(比如,如果我做了一个.foreach和一个返回 – 它会更清晰和更清晰),最糟糕的是,它遍历整个迭代,即使遇到一个非偶数。

那么写这样一个折叠的最好方法是什么? 我应该去recursion地写这个,还是有一个更可接受的方法?

我的第一个select通常是使用recursion。 它只是略微不那么紧凑,可能更快(当然不会更慢),并且在提前终止时可以使逻辑更加清晰。 在这种情况下,你需要嵌套defs有点尴尬:

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

我的第二个select是使用return ,因为它保持一切完好,你只需要在def折叠,这样你就可以返回一些东西 – 在这种情况下,你已经有了一个方法,所以:

 def sumEvenNumbers(nums: Iterable[Int]): Option[Int] = { Some(nums.foldLeft(0){ (n,x) => if ((n % 2) != 0) return None n+x }) } 

在这种情况下,它比recursion更紧凑(虽然由于我们必须做一个iterable / iterator转换,所以我们得到了特别不幸的recursion)。 当所有其他东西相等时,这个跳跃的控制stream程是可以避免的,但这里不是。 在有价值的情况下使用它没有什么坏处。

如果我经常这样做,并希望它在一个方法的中间(所以我不能只使用返回),我可能会使用exception处理来生成非本地控制stream。 毕竟,它擅长什么,error handling不是唯一有用的时间。 唯一的技巧是避免生成堆栈跟踪(这非常慢),这很容易,因为特征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放在任何地方,而不仅仅是包装整个方法。

接下来对我来说,将是重新实施折叠(或者我自己或者find一个库),这样可能意味着提前终止。 这样做的两种自然方式是不传播值,而是包含值的Option ,其中None意味着终止; 或者使用信号完成的第二个指示器function。 Kim Stebel所示的Scalaz懒惰的折叠已经覆盖了第一个案例,所以我将展示第二个(带有可变的实现):

 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)(_ + _) 

(无论你是通过recursion,退货还是懒惰来实现终止都取决于你。)

我认为这涵盖了主要的合理变体; 还有一些其他的select,但我不知道为什么会在这种情况下使用它们。 (如果Iterator有一个findOrPreviousIterator本身就可以工作,但是它不会,而且手工完成这项工作需要额外的工作,所以在这里使用它是一个愚蠢的select。)

你描述的场景(退出一些不需要的条件)似乎是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也可以。 我build议你的“偶数总和,但打破奇数”的解决scheme是:

 list.iterator.takeWhile(_ % 2 == 0).foldLeft(...) 

而只是为了certificate,一旦击中奇数,不会浪费你的时间。

 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中使用foldRight的懒版本来实现你想要的function风格。 有关更深入的解释,请参阅此博客文章 。 虽然此解决scheme使用Stream ,但可以使用iterable.toStream有效地将Iterable转换为Stream

 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 

这清楚地表明匿名函数只被调用两次(即直到遇到奇数)。 这是由于foldr的定义,其签名(在Stream情况下)是def foldr[B](b: B)(f: (Int, => B) => B)(implicit r: scalaz.Foldable[Stream]): B 。 请注意,匿名函数采用名称参数作为其第二个参数,所以不需要评估。

顺便说一句,你仍然可以用OP的模式匹配解决scheme写这个,但是我发现if / else和map更优雅。

那么,斯卡拉确实允许非本地回报。 对于这是不是一个好的风格有不同的意见。

 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) 

编辑:

在这种情况下,正如@Arjan所build议的那样,你也可以这样做:

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

@克雷克尔你的答案帮助了我,但我需要调整它使用任一

  
   def foldOrFail [A,B,C,D](map:B =>或者[D,C])(合并:(A,C)=> A)(initial:A)(it:Iterable [B]):或者[D,A] = {
     val ii = it.iterator
     var b =初始值
     while(ii.hasNext){
       val x = ii.next
      地图(x)匹配{
        案例左(错误)=>返回左(错误)
        情况右(d)=> b =合并(b,d)
       }
     }
    右(B)
   }

您可以尝试使用临时variables并使用takeWhile。 这是一个版本。

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

在这种情况下evenSum应该是Some(20)

您可以在遇到终止标准时抛出一个精心挑选的exception,并在调用代码中处理它。

更好的解决scheme是使用跨度:

 val (l, r) = numbers.span(_ % 2 == 0) if(r.isEmpty) Some(l.sum) else None 

…但是如果所有的数字都是偶数,它会遍历这个列表两次

只是为了“学术”的原因(:

 var headers = Source.fromFile(file).getLines().next().split(",") var closeHeaderIdx = headers.takeWhile { s => !"Close".equals(s) }.foldLeft(0)((i, S) => i+1) 

采取两次,然后它应该,但它是一个不错的class轮。 如果没有find“closures”,它将返回

 headers.size 

另一个(更好)是这一个:

 var headers = Source.fromFile(file).getLines().next().split(",").toList var closeHeaderIdx = headers.indexOf("Close")