我解决了项目Euler问题,然后用Haskell维基上的问题面对我的解决方案。他们非常相似,但是我的时间是7.5秒,而另外0.6!我把它们都编成了。
我看起来如下:
main = print . maximumBy (compare `on` cycleLength) $ [1..999]
where cycleLength d = remainders d 10 []
和其中一个维基:
main = print . fst $ maximumBy (comparing snd) [(n, cycleLength n) | n <- [1..999]]
where cycleLength d = remainders d 10 []
我还尝试使用compare `on`
更改comparing cycleLength
,但效果保持不变
因此,我必须得出结论,所有差异都在于计算运行中的值与在列表理解中进行转换。
但时间上的差异非常大:第二个版本的加速速度提高了12.5倍!
答案 0 :(得分:11)
maximumBy
函数会多次重复检查列表中的相同数字 - 每次检查一个数字时,都必须重新计算 cycleLength
。这是一项昂贵的操作!
wiki算法因此使用称为decorate-sort-undecorate的技术。现在,在这里你没有排序,但它足够接近。您首先预先计算所有数字的cycleLength
值(即您创建'缓存')然后执行最大操作,然后然后您将其解包(使用fst
。)这样,你可以省去很多计算!
编辑:要说明一下,请查看maximumBy
来源中的Data.List
函数:
-- | The 'maximumBy' function takes a comparison function and a list
-- and returns the greatest element of the list by the comparison function.
-- The list must be finite and non-empty.
maximumBy :: (a -> a -> Ordering) -> [a] -> a
maximumBy _ [] = error "List.maximumBy: empty list"
maximumBy cmp xs = foldl1 maxBy xs
where
maxBy x y = case cmp x y of
GT -> x
_ -> y
它在2的窗口中移动;请求每个号码(在您的情况下计算)两次。
这意味着对于999次迭代,您的版本将调用cycleLength d
1996次(n * 2-2),而维基版本将调用它999(n)次。
这并不能解释完全延迟 - 只有2倍,但因子更接近于10。
以下是您的版本的个人资料,
COST CENTRE entries %time %alloc %time %alloc
MAIN 0 0.0 0.0 100.0 100.0
CAF 0 0.0 0.0 100.0 100.0
main 1 0.0 0.0 100.0 100.0
f 1 0.0 0.0 100.0 100.0
maximumBy 1 0.0 0.0 100.0 99.9
maximumBy.maxBy 998 0.0 0.1 100.0 99.9
cycleLength 1996 0.1 0.2 100.0 99.8
remainders 581323 99.3 94.4 99.9 99.7
remainders.r' 581294 0.7 5.2 0.7 5.2
和wiki版本:
COST CENTRE entries %time %alloc %time %alloc
MAIN 0 0.0 0.0 100.0 100.0
CAF 0 0.0 0.0 100.0 99.9
main 1 0.0 0.1 100.0 99.9
f' 1 0.0 0.8 100.0 99.8
cycleLength 999 0.2 0.5 100.0 98.6
remainders 95845 98.3 93.0 99.8 98.2
remainders.r' 95817 1.5 5.2 1.5 5.2
maximumBy 1 0.0 0.1 0.0 0.4
maximumBy.maxBy 998 0.0 0.2 0.0 0.2
查看此处的配置文件,您的版本似乎经历了更多的分配(大约10到12倍),但总体上没有使用更多的RAM。因此,我们需要根据分配来解释累积因子5或6。
剩余是递归的。在您的示例中,它被称为581294次。在wiki示例中,它被调用了95817次。我们增加了5-6倍!
所以我认为这里的compare
电话也是一个问题。因为它将cycleLength
应用于我们想要比较的两件事情!在wiki问题中,cycleLength
会应用于每个数字,但是在这里,我们将它应用于每个数字两次,和比较似乎更频繁地应用,这是一个问题特别是数字越大,因为remainders
的复杂性很差(似乎是指数级的,但我不确定。)
由于两个程序的最大内存消耗并没有太大的不同,我认为这与堆没有任何关系。