我是Haskell noob。在尝试循环和列表推导时,我发现列表理解比循环更快。 Lisp理解使用取幂并且仍然更快。为什么呢?
lg :: Int -> Int -> Int -> Int
lg s e a =
if s < e
then
if even s
then
lg (s+1) e (a+1)
else
lg (s+1) e (a*2)
else
a
loopGrowth :: Int -> Int
loopGrowth x = lg 0 x 0
calcGrowth :: Int -> Int
calcGrowth y =
last (take (y+1) (tail (join [ [2 ^ x - 2 ,2 ^ x - 1] | x <- [1..50] ])))
答案 0 :(得分:5)
首先是一些风格建议:
lg s e a
| s >= e = a
| even s = lg (s+1) e (a+1)
| otherwise = lg (s+1) e (a*2)
关于你的问题:lg
实际上不是一个循环。它是一个尾递归函数,但仅此一点在Haskell中并没有多少说明。阻碍的主要因素是懒惰:如果累加器只是在thunk维度而不是它们的值中累积,那么懒惰只会产生大量的开销而没有任何可用性。
防止这种情况的一种简单方法是使用
严格评估参数{-# LANGUAGE BangPatterns #-}
lg' s e !a
| s >= e = a
| even s = lg' (s+1) e (a+1)
| otherwise = lg' (s+1) e (a*2)
然而,由于这些问题,但也因为简洁/模块化/ ...,最好不要编写尾递归函数,而是使用更高级别的工具 - 列表推导本身通常不性能很好,但如果你用通用折叠等方式表达你的算法,你可以利用更多高性能的数据结构,例如未装箱的向量,而你的代码几乎没有变化。
答案 1 :(得分:4)
除了@leftaroundabout提到的更复杂的thunk问题之外,我可以想到两个算法的原因,为什么列表理解可能比你想象的更有效:
^
比简单地在循环中相乘更聪明:它在内部使用2除法来大大减少所需的乘法次数。例如。 x^8
变为&#34;基本上&#34; let x2 = x*x; x4 = x2*x2 in x4*x4
。 (编辑:正如@dfeuer指出的那样,这被称为&#34;快速取幂&#34;或通过平方&#34;&#34;取幂;)