【问题标题】:foldLeft early termination in a Stream[Boolean]?Stream[Boolean] 中的 foldLeft 提前终止?
【发布时间】:2013-01-31 04:29:30
【问题描述】:

我有一个:

val a : Stream[Boolean] = ...

当我按如下方式折叠时

val b = a.foldLeft(false)(_||_)

当它在流中找到第一个true 值时会终止吗?如果没有,我该怎么做?

【问题讨论】:

标签: scala


【解决方案1】:

它不会在第一个 true 时终止。您可以改用存在:

val b = a.exists(identity)

【讨论】:

  • 我很确定@Duc 发布的代码只是说明性的,在他的实际用例中,他确实需要以某种方式累积值。所以exists 不符合要求。
  • @RégisJean-Gilles 有什么方法可以累积无法使用的布尔值?
  • 通过实际用例,我的意思是他不只是想 OR 流一个布尔值。
  • @RégisJean-Gilles 那么真正的用例是什么?
  • 所以我将for (i <- 0 to n-1 if p(i)) return true 更改为(for (i <- Stream.range(0, n)) yields p(i)).exists(_ == true) 但它变得慢了很多。我在这里做错了什么?
【解决方案2】:

不,它不会提前终止。这很容易说明:

val a : Stream[Boolean] = Stream.continually(true)
// won't terminate because the strea
val b = a.foldLeft(false)(_||_)

stew 表明,在您的具体情况下,提前终止的简单解决方案是

val b = a.exists(identity).

更简单,这相当于:

val b = a.contains(true)

如果您确实需要折叠,则与上述不同的更通用的解决方案也适用,即使用递归(请注意,为简单起见,这里我假设流是非空的):

def myReduce( s: Stream[Boolean] ): Boolean = s.head || myReduce( s.tail )
val b = myReduce(a)

现在递归解决方案的有趣之处在于它如何用于更一般的用例中,您实际上需要以某种方式累积值(这就是 fold 的用途)并且仍然提前终止。假设您想使用add 方法添加整数流的值,该方法将以类似于|| 的方式提前“终止”(在这种情况下,如果左手,它不会评估其右手边边大于 100):

def add(x: Int, y: => Int) = if ( x >= 100 ) x else x + y
val a : Stream[Int] = Stream.range(0, Int.MaxValue)
val b = a.foldLeft(0)(add(_, _))

最后一行不会终止,就像您的示例一样。但是你可以这样修复它:

def myReduce( s: Stream[Int] ): Int = add( s.head, myReduce( s.tail ) )
val b = myReduce(a)

警告:这种方法有一个明显的缺点:myReduce 这里不是尾递归,这意味着如果迭代流的太多元素,它会破坏你的堆栈。 另一个不会破坏堆栈的解决方案是:

val b = a.takeWhile(_ <= 100).foldLeft(0)(_ + _)

但我担心我在离题方面走得太远了,所以我最好现在停下来。

【讨论】:

  • 你写的是foldRight,它在Stream上无论如何都不会是尾递归的。有关 foldRight 的解决方案,请参阅 this answer,它概括了您使用的相同想法,同时保持实际折叠函数的参数化。
  • 感谢您的链接。我不会再次更新答案,因为我说过我的答案在最后已经几乎是题外话了。使用我的答案加上您链接到的答案对任何人来说都绰绰有余。信不信由你,但myReduce 的第一个版本(应用于布尔流的那个)实际上是尾递归的(与第二个版本的主要区别在于|| 是一个内置 短路运算符)!
  • 我基于您的评论。但我明白你的意思:最后,|| 必须编译为与 if 短路相同的字节码(如 Java 所做的那样),因此它是尾递归的。 Scala 正确地实现了这一点,有点令人惊讶:import annotation.tailrec; @tailrec def myReduce( s: Stream[Boolean] ): Boolean = s.head || myReduce( s.tail )
【解决方案3】:

您可以使用takeWhile 提取您要对其进行操作的Stream 的前缀,然后将foldLeft 应用于该前缀。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-11-27
    • 2014-08-04
    • 2017-03-11
    • 2014-06-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多