经过漫长的休息后,我正在给自己一个关于Haskell的复习。我正在研究99 Haskell Problem。我为#5编写了一个解决方案,然后看一下alternative solutions来扩展我的知识。以下是#5的解决方案页面的摘录:
撤消列表。
前言中的标准定义简明扼要,但不是 很可读。另一种定义反向的方法是:
reverse :: [a] -> [a] reverse [] = [] reverse (x:xs) = reverse xs ++ [x]
然而,这个定义比Prelude中的定义更浪费 在累积结果时反复重新计算结果。下列 变异避免了这种情况,因而在计算上更接近Prelude 版本
reverse :: [a] -> [a] reverse list = reverse' list [] where reverse' [] reversed = reversed reverse' (x:xs) reversed = reverse' xs (x:reversed)
有人可以解释为什么第二种解决方案比第一种解决方案更有效吗?我可以看到两种解决方案都使用递归,但我还不能理解差异如何影响效率,是否与Haskell遍历列表有关?
答案 0 :(得分:3)
第一个解决方案使用递归,但它不是尾递归,递归调用是函数执行的最后一个。尾递归比非尾递归更有效,因为函数不需要在函数调用后继续执行,因此它的堆栈帧等可以重用。
第一个解决方案首先执行递归调用,然后在结果和单个元素列表上调用连接运算符。第二个解决方案首先将元素添加到列表中,然后再次调用自身,而无需进一步执行任何操作。
除此之外,++
需要第一个参数长度的线性时间,因为它需要复制列表的每个节点,以便它可以将最后一个节点更改为指向第一个节点的第一个节点。右侧列表。这是因为第一个变量在内部完成了它的实际工作(首先它以递归方式调用自身,然后在返回之后,它执行实际工作,这意味着内部调用将首先完成它们的工作),或者换句话说,它处理个别列表元素从右到左。因此,当它遇到一个新元素(在原始列表的左侧更远)时,需要将它添加到列表的右侧。在Haskell列表中,添加到右侧是昂贵的,添加到左侧是便宜的。
第二个解决方案从外到内或从左到右工作,这是此问题的正确执行顺序。