在 Scala 中创建 Stream 的递归方法
这是我之前的问题的后续问题。
据我理解,以下计算斐波那契数的方法效率低下,因为调用了方法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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
不,尾递归是为了帮助编译器循环而不是堆栈(全局),这是一种编译时优化。
问题来自第一个实现,其中对 fib 的多次调用导致多次 Stream 构造,因此一遍又一遍地进行相同的演算。
如果您想查看它,请尝试以下操作
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.If you want to see it try the following
我尝试改进 Andy 的 答案,但他几乎做到了。第一个解决方案是创建一个流金字塔——每次调用
fib
都会创建另一个斐波那契流,并且每个新流都会自己创建新流,依此类推。需要明确的是,调用
fib
会产生三个个流:fib
在fib zip fib.tail< 中创建/code>
fib.tail
在fib zip fib.tail
创建的一个map
创建的(记住,map
创建一个新集合)因为前两个是调用
fib
,他们将分别创建三个流,依此类推。这是它的粗略“图片”:
这种情况一直持续下去。中间流是使用其左侧和右侧的最高流(fib 和 fib.tail)计算的。它们中的每一个都是使用其左侧和右侧的较低流来计算的。这些较低的流中的每一个都是使用最后一行显示的流来计算的。
我们可以继续这样下去,但是你可以看到,当我们计算 8 时,我们已经有 14 个其他斐波那契流在进行。
如果将其从
def
更改为val
,所有这些新流都会消失,因为fib
和fib.tail
将引用现有流而不是创建新流。由于不会创建新的流,因此不会进一步调用fib
和fib.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
:fib
infib zip fib.tail
fib.tail
infib zip fib.tail
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:
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
toval
, all these new streams disappear, becausefib
andfib.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 tofib
andfib.tail
.Now, if you look at the second answer, you'll notice that there is a single
fib
call, and nomap
or similar method, so there's no multiplication effect.