我正在尝试解决项目eulers的第15个问题,格子路径(http://projecteuler.net/problem=15)。
我的第一次尝试是逐行解决问题,然后取最后一行中的最后一个元素。
number_of_ways_new_line last_line = foldl calculate_values [] last_line
where
calculate_values lst x = lst ++ [(if length lst > 0 then (last lst) + x else head last_line)]
count_ways x = foldl (\ lst _ -> number_of_ways_new_line lst) (take x [1,1..]) [1..x-1]
result_15 = last $ count_ways 21
这很有效,而且速度很快,但我觉得这很难看。所以我想了一会儿,想出了一个更惯用的功能(如果我弄错了,请纠正我),使用递归来解决问题:
lattice :: Int -> Int -> Int
lattice 0 0 = 1
lattice x 0 = lattice (x-1) 0
lattice 0 y = lattice (y-1) 0
lattice x y
| x >= y = (lattice (x-1) y) + (lattice x (y-1))
| otherwise = (lattice y (x-1)) + (lattice (y-1) x)
这适用于短数字,但它根本不能缩放。我使用lattice 1 2
和lattice 2 1
始终相同的事实对其进行了优化。为什么这么慢?难道Haskell不会记住以前的结果,所以每当调用lattice 2 1
时它会记住旧的结果吗?
答案 0 :(得分:2)
现在这个问题可以在数学上解决,将重复关系操作为封闭形式,但我会关注更有趣的问题,记忆。
首先我们可以使用Data.Array
(这是懒惰的)
import Data.Array as A
lattice x y = array ((0, 0), (x, y)) m ! (x, y)
where m = [(,) (x, y) (lattice' x y) | x <- [0..x], y <- [0..y]
lattice' 0 0 = 1
lattice' x 0 = lattice (x-1) 0
lattice' 0 y = lattice (y-1) 0
lattice' x y | x >= y = (lattice (x-1) y) + (lattice x (y-1))
| otherwise = (lattice y (x-1)) + (lattice (y-1) x)
现在这些重复遍历地图,但是,由于地图是懒惰的,一旦评估了地图条目,它的thunk将变为一个简单的值,确保它只计算一次。
我们也可以使用精彩的memo-combinators
库。
import Data.MemoCombinators as M
lattice = memo2 M.integral M.integral lattice'
where lattice' 0 0 = 1
lattice' x 0 = lattice (x-1) 0
lattice' 0 y = lattice (y-1) 0
lattice' x y | x >= y = lattice (x-1) y + lattice x (y-1)
| otherwise = lattice y (x-1) + lattice (y-1) x
答案 1 :(得分:1)
这个答案确实很慢,因为它同时依赖于多次递归。
让我们检查lattice
函数。它有什么作用?它返回两个lattice
函数的总和,另一个lattice
函数或第一个函数。
现在让我们举一个例子:lattice 2 2
这等于lattice 1 2 + lattice 2 1
这等于lattice 0 2 + lattice 1 1 + lattice 1 1 + lattice 2 0
这等于......
1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + ... + 1
这笔钱是你的最终结果。但是,上述总和中的每1个都是一个或多个函数调用的结果。因此,您的功能将比结果更有价值。
现在考虑lattice 20 20
产生大约1500亿(这是一个估计,所以我不会破坏太多)。
这意味着您的功能将被评估大约150亿次。
让人惊讶。
不要为这个错误感到难过。我曾经说过:
fibbonaci x = fibbonaci (x-1) + fibbonaci (x-2)
我鼓励你弄清楚为什么这是个坏主意。
PS很高兴他们没有要求lattice 40 40
。那将会超过10 ^ 23个函数调用,或者至少300万年。
PPS通过仅计算一侧的加速度只会减慢执行速度,因为函数调用的数量不会减少,但每个函数调用的时间会减少。
免责声明:这是我见过的第一块Haskell代码。