中止提前
什么是尽早终止弃牌的最好方法? 作为一个简单的例子,想象一下我想总结一个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
有一个findOrPrevious
, Iterator
本身就可以工作,但是它不会,而且手工完成这项工作需要额外的工作,所以在这里使用它是一个愚蠢的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")