【问题标题】:Merge two Streams (ordered) to get a final sorted Stream合并两个流(有序)以获得最终排序的流
【发布时间】:2015-04-13 11:01:08
【问题描述】:

例如,如何合并两个已排序整数的 Streams?我认为这是非常基本的,但发现它一点也不简单。下面的不是尾递归的,当Streams很大时它会堆栈溢出。

def merge(as: Stream[Int], bs: Stream[Int]): Stream[Int] = {
  (as, bs) match {
    case (Stream.Empty, bss) => bss
    case (ass, Stream.Empty) => ass
    case (a #:: ass, b #:: bss) =>
      if (a < b) a #:: merge(ass, bs)
      else b #:: merge(as, bss)
  }
}

我们可能想通过引入累加器将其变成尾递归。但是,如果我们预先挂起累加器,我们只会得到一个倒序的流;如果我们用串联 (#:::) 附加累加器,它就不再是惰性(严格)了。

这里有什么解决方案?谢谢

【问题讨论】:

  • "下面的不是尾递归的,当Streams很大时它会堆栈溢出。"为什么?递归不是“真实的”,因为不应该评估“递归”调用中的合并。也就是说,您的代码根本不是递归的,因为它从 if? 的任一分支返回一个新流?还是我误会了?

标签: scala stream lazy-evaluation tail-recursion


【解决方案1】:

把评论变成答案,你的合并没有问题。

它根本不是递归的——任何一次合并调用都会返回一个新的流,而不需要任何其他合并调用。 a #:: merge(ass, bs) 返回一个带有第一个元素 a 的流,并且在需要时将调用 merge(ass, bs) 来评估流的其余部分。

所以

val m = merge(Stream.from(1,2), Stream.from(2, 2))
//> m  : Stream[Int] = Stream(1, ?)
m.drop(10000000).take(1)
//> res0:     scala.collection.immutable.Stream[Int] = Stream(10000001, ?)

工作得很好。没有堆栈溢出。

【讨论】:

  • 感谢您的回答。问题是,由于它不是尾递归的,因此您上面的示例代码将 OOM,因为我猜堆栈帧将包含对流头部的引用。
  • 我不确定这是否正确(但我没有测试过)。一旦你访问了流中的下一个元素,前一个堆栈帧/闭包将被取消引用并成为 GC;我认为。
猜你喜欢
  • 1970-01-01
  • 2018-05-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-05-23
  • 1970-01-01
  • 2019-12-06
  • 2015-11-12
相关资源
最近更新 更多