在 Scala 中创建 Stream 的递归方法

发布于 2024-12-24 18:09:19 字数 694 浏览 1 评论 0原文

这是我之前的问题的后续问题。

据我理解,以下计算斐波那契数的方法效率低下,因为调用了方法fib每个斐波那契数以及每次调用它时都会创建一个新流。

def fib:Stream[Int] =
  Stream.cons(1, Stream.cons(1, (fib zip fib.tail) map {case (x, y) => x + y}))

另一方面,尾递归方法(如这里)看起来非常高效,并在 O(1) 中计算每个斐波那契数

def fib(a:Int, b:Int):Stream[Int] = Stream.cons(a, fib(b, a+b));

现在我得出结论,递归方法当且仅当它们是尾递归时,create Streams 才是高效的。正确吗?

This is a follow-up to my previous question.

As I understand the following method for calculating Fibonacci numbers is inefficient since the method fib is called for each Fibonacci number and each time it is called it creates a new stream.

def fib:Stream[Int] =
  Stream.cons(1, Stream.cons(1, (fib zip fib.tail) map {case (x, y) => x + y}))

On other hand the tail recursive method (as in here) looks quite efficient and calculates each Fibonacci number in O(1)

def fib(a:Int, b:Int):Stream[Int] = Stream.cons(a, fib(b, a+b));

Now I am concluding that recursive methods that create Streams are efficient if and only if they are tail recursive. Is it correct?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

已下线请稍等 2024-12-31 18:09:19

不,尾递归是为了帮助编译器循环而不是堆栈(全局),这是一种编译时优化。

问题来自第一个实现,其中对 fib 的多次调用导致多次 Stream 构造,因此一遍又一遍地进行相同的演算。

fib zip fib.tail
//if we are at the 1000, it will compute million Streams

如果您想查看它,请尝试以下操作

var i = 0
def fib:Stream[Int] = {
  i = i + 1
  println("new Stream : " + i)
  Stream.cons(1, Stream.cons(1, (fib zip fib.tail) map {case (x, y) => x + y}))
}

No, tail recursive is to help the compiler looping instead of stacking (globally), it's a compile time optimization.

The problem came from the first implementation where several calls to fib leads to several Stream constructions, so the same calculus are made over and over.

fib zip fib.tail
//if we are at the 1000, it will compute million Streams

If you want to see it try the following

var i = 0
def fib:Stream[Int] = {
  i = i + 1
  println("new Stream : " + i)
  Stream.cons(1, Stream.cons(1, (fib zip fib.tail) map {case (x, y) => x + y}))
}
落叶缤纷 2024-12-31 18:09:19

我尝试改进 Andy答案,但他几乎做到了。第一个解决方案是创建一个流金字塔——每次调用fib都会创建另一个斐波那契流,并且每个新流都会自己创建新流,依此类推。

需要明确的是,调用 fib 会产生三个个流:

  • 一个由 fibfib zip fib.tail< 中创建/code>
  • fib.tailfib zip fib.tail 创建的一个
  • map 创建的(记住,map 创建一个新集合)

因为前两个是调用fib,他们将分别创建三个流,依此类推。

这是它的粗略“图片”:

                          1
                          1
          1               2               1
          1               3       1       2       1
    1     2       1       5       1       3   1   2   1
    1     3   1   2   1   8   1   2   1   5   1   3 1 2 1                          

这种情况一直持续下去。中间流是使用其左侧和右侧的最高流(fib 和 fib.tail)计算的。它们中的每一个都是使用其左侧和右侧的较低流来计算的。这些较低的流中的每一个都是使用最后一行显示的流来计算的。

我们可以继续这样下去,但是你可以看到,当我们计算 8 时,我们已经有 14 个其他斐波那契流在进行。

如果将其从 def 更改为 val,所有这些新流都会消失,因为 fibfib.tail 将引用现有流而不是创建新流。由于不会创建新的流,因此不会进一步调用 fibfib.tail

现在,如果您查看第二个答案,您会注意到只有一个 fib 调用,并且没有 map 或类似方法,因此没有乘法效果。

I've tried to improve on Andy's answer, but he pretty much nailed it. The first solution is creating a pyramid of streams -- each call to fib creates another fibonacci stream, and each of these new streams will create new stream themselves, and so on.

To be clear, there are three streams resulting from a call to fib:

  • One created by fib in fib zip fib.tail
  • One created by fib.tail in fib zip fib.tail
  • One created by map (remember, map creates a new collection)

Since the first two are calls to fib, they'll create three streams each, and so on.

Here's a rough "picture" of it:

                          1
                          1
          1               2               1
          1               3       1       2       1
    1     2       1       5       1       3   1   2   1
    1     3   1   2   1   8   1   2   1   5   1   3 1 2 1                          

And this goes on and on. The middle stream is computed using the highest streams to its left and right (fib and fib.tail). Each of them is computed using lower streams on their left and right. Each of these lower streams is computed using streams shown on the last line.

We could continue this on and on, but you can see that, by the time we computed 8, we already have 14 other fibonacci streams going on.

If you change it from def to val, all these new streams disappear, because fib and fib.tail will refer to an existing stream instead of creating new streams. And since no new stream will be created, there will be no further calls to fib and fib.tail.

Now, if you look at the second answer, you'll notice that there is a single fib call, and no map or similar method, so there's no multiplication effect.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文