【发布时间】:2011-03-28 14:20:37
【问题描述】:
我有一个 F# 函数,它以跳过 n、选择 n、跳过 n、选择 n... 的模式返回一个从 0 开始的数字列表,直到一个限制。例如,输入 2 的此函数将返回 [2, 3, 6, 7, 10, 11...]。
最初我将其实现为一个非尾递归函数,如下所示:
let rec indicesForStep start blockSize maxSize =
match start with
| i when i > maxSize -> []
| _ -> [for j in start .. ((min (start + blockSize) maxSize) - 1) -> j] @ indicesForStep (start + 2 * blockSize) blockSize maxSize
认为尾递归是可取的,我使用累加器列表重新实现它,如下所示:
let indicesForStepTail start blockSize maxSize =
let rec indicesForStepInternal istart accumList =
match istart with
| i when i > maxSize -> accumList
| _ -> indicesForStepInternal (istart + 2 * blockSize) (accumList @ [for j in istart .. ((min (istart + blockSize) maxSize) - 1) -> j])
indicesForStepInternal start []
但是,当我在 Mono 下的 fsi 中使用参数 1、1 和 20,000(即应返回 [1, 3, 5, 7...] 最多 20,000)运行此程序时,尾递归版本明显慢于第一个版本(与第一个版本相比为 12 秒)亚秒)。
为什么尾递归版本更慢?是因为列表连接吗?是编译器优化吗?我真的用尾递归实现了吗?
我也觉得我应该使用高阶函数来做到这一点,但我不确定具体如何去做。
【问题讨论】:
-
不幸的是,我没有时间提供替代代码,但有一些快速观察:1)它是尾递归的,2)列表追加是 O(n),因此效率低下。我建议反转(逐步降低)您的列表理解,将其转换为 accumList,并在您的第一个模式匹配中返回它之前反转 accumList。
-
非常感谢大家;这些答案非常有帮助、信息丰富且具有教育意义。
标签: performance f# tail-recursion