如何解决长度函数的尾递归版本是否在运行时节省了堆栈空间?
let rec len xs =
match xs with
| [] -> 0
| x::xr -> 1 + len xr;;
虽然这不是一个困难的练习,但我想知道是否更改为尾递归版本,例如下面的版本,
let rec leni xs r =
match xs with
| [] -> r
| x::xr -> leni xr r+1;;
与非尾递归相比,确实在运行时节省了堆栈空间?
解决方法
尾递归函数被编译成一个循环,它们不使用任何其他依赖于迭代次数的堆栈。
A,您的版本不是尾递归的,因为您将运算符的优先级设置为错误。累加器r
被解释为属于递归调用,它未更改地传递给该递归调用。因此,该函数需要返回以增加其返回值。
让我们看看:
let rec len xs =
match xs with
| [] -> 0
| x::xr -> 1 + len xr;;
let rec leni xs r =
match xs with
| [] -> r
| x::xr -> leni xr r+1;;
[0..10000] |> len // val it : int = 10001
[0..100000] |> len // val it : int = 100001
[0..1000000] |> len // Process is terminated due to StackOverflowException.
([0..1000000],0) ||> leni // Process is terminated due to StackOverflowException.
解决方法是简单地将新的累加器值括在括号中,并将其加1。
let rec leni' xs r =
match xs with
| [] -> r
| x::xr -> leni' xr (r+1)
([0..1000000],0) ||> leni' // val it : int = 1000001
您可以继续使用Continuation Passing Style(CPS),用组合函数代替累加器,每个累加函数的自变量都加1。这也将编译成一个循环并保留堆栈空间,但以存储功能链所需的内存为代价。
此外,您可以重新考虑参数的顺序:首先使用累加器(或继续符),最后使用列表,以允许使用function
关键字。
let rec lenk k = function
| [] -> k 0
| x::xr -> lenk (k << (+) 1) xr
[0..1000000] |> lenk id // val it : int = 1000001
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。