我对解决问题的实际解决方案或其他方法不感兴趣,这是我需要帮助的备忘录:)
我需要帮助用memoization做一个帕斯卡三角问题。我想得到三角形底部的中间数字。 (项目欧拉15)
第一个例子没有记忆(虽然名字暗示如此)“20 20”不可解决
第二次尝试是尝试做类似于:http://www.haskell.org/haskellwiki/Memoization
的事情如果对某人更具可读性,则第三个是对no2的建议。
我得到了这个错误,但我不确定它是否正确,即使它会编译...(从ghci以2 2作为参数运行
no instance for (Num [a0])
arising from a use of `zmemopascals'
Possible fix: add an instance declaration for (Num [a0])
In the expression: zmemopascals 2 2
In an equation for `it': it = zmemopascals 2 2
Code:
--1
memopascals r c = [[pascals a b | b<-[1..c]] | a<-[1..r]] !! (r-1) !! (c-1)
where pascals 1 _ = 1
pascals _ 1 = 1
pascals x y = memopascals (x-1) y + memopascals x (y-1)
--2
--xmemopascals :: Int -> Int -> Int
xmemopascals r c = map (uncurry pascals) (zip [1..] [1..]) !! (r-1) !! (c-1)
where pascals 1 _ = 1
pascals _ 1 = 1
pascals x y = memopascals (x-1) y + memopascals x (y-1)
--3
zmemopascals r c = zipWith pascals [1 ..] [1 ..] !! (r-1) !! (c-1)
where pascals 1 _ = 1
pascals _ 1 = 1
pascals x y = memopascals (x-1) y + memopascals x (y-1)
答案 0 :(得分:2)
Memoization在您的函数中不起作用,因为像memopascals 5 5
之类的调用在内部构建三角形的一部分并从中返回单个值。另一个电话mempascals 6 6
与memopascals 5 5
内部的部分三角形没有任何关联。
对于有效的memoization,您需要一个单独的函数中的公共部分(如表示三角形中计算的数字的列表列表),然后由访问特定索引的函数使用。这样,您可以使用相同的列表列表来查找不同的索引。
通常最简单的方法是编写一个函数,如fullpascals :: [[Int]]
,生成完整的(无限的)数据结构,而不是另一个函数来访问该数据结构,如
memopascals x y = fullpascals !! (x-1) !! (y-1)
在您的情况下,fullpascals
将与您当前的某个函数相同,但没有特定索引的参数。它甚至可以在内部使用memopascals
进行递归。
旁注:在wiki的memoized_fib
示例中,记忆的“公共部分”并不是所有fib的列表,而是一个访问列表的函数所有的纤维。效果是一样的,因为编译器足够聪明,可以优化它。
答案 1 :(得分:1)
zmemopascals r c = zipWith pascals [1 ..] [1 ..] !! (r-1) !! (c-1)
where pascals 1 _ = 1
pascals _ 1 = 1
pascals x y = memopascals (x-1) y + memopascals x (y-1)
并不重要,但在最后一行,您要拨打zmemopascals
而不是memopascals
。
在第一行中,您有两个列表索引运算符。因此,对于某些zipWith pascals [1 .. ] [1 .. ]
,[[a]]
必须具有a
类型。 pascals
的定义是
pascals :: Num t => Int -> Int -> t
因此zipWith pascals [1 .. ] [1 .. ] :: [t]
。因此类型推断发现t = [a]
,但编译器找不到实例Num [a]
。
对于memoisation,你必须给整个三角形命名,就像@sth建议的那样,以避免重新计算(Fibonacci memoisation只能起作用,因为编译器足够聪明,它的形式无法保证)。
另一种选择是使用iterate
,
pascal :: Int -> Int -> Integer
pascal n k = iterate nextRow [1] !! n !! k
where
nextRow ks = zipWith (+) (0:ks) (ks ++ repeat 0)
答案 2 :(得分:1)
实现备忘录有几条准则(最近的讨论看看here)。
首先,使用-O2优化标志与GHC编译器。其次,使用单形类型签名。命名您想要实现共享的中间列表。
然后,注意你的嵌套定义。如果嵌套定义依赖于其封闭(“外部”)作用域中的参数值,则意味着对该外部函数的每次调用都必须重新创建其所有嵌套定义,因此不会有任何 one < / em>要共享的列表,但许多单独的独立列表。
在你的函数中,分离并命名你想要共享的列表表达式,我们得到
memopascals r c = xs !! (r-1) !! (c-1)
where
xs = [[pascals a b | b<-[1..c]] | a<-[1..r]]
pascals 1 _ = 1
pascals _ 1 = 1
pascals x y = memopascals (x-1) y + memopascals x (y-1)
您的xs
定义依赖关于r
和c
的值,但您可以调用“外部”函数memopascals
,在嵌套的pascals
内。每次memopascals
的调用都有来创建自己的xs
副本,因为它取决于memopascals
的参数,r
和{{1} }。不可分享。
如果您需要具有该依赖定义,则必须安排不要调用“超出范围”,而是保持在该范围内,以便可以重用所有内部(“嵌套”)定义。
c
现在,memopascals r c = f r c
where
f r c = xs !! (r-1) !! (c-1)
xs = [[pascals a b | b<-[1..c]] | a<-[1..r]]
pascals 1 _ = 1
pascals _ 1 = 1
pascals x y = f (x-1) y + f x (y-1)
的每次调用都会创建其内部定义(来自其嵌套作用域),然后相互调用,从不调用范围 - 因此列表memopascals
会被重用,从而实现共享。
但是对xs
的另一次调用将创建自己的列表memopascals
内部定义的新副本,并将使用 it 。我们可以将其称为“本地”共享,而不是“全局”共享(即备忘录)。这意味着它运行速度很快,但是使用相同参数的第二次调用与第一次调用时间相同 - 而不是像完全存储器那样的0时间。
实现 只有一种方法,那就是让你的xs
定义独立。然后编译器可以将所有嵌套的范围框架粉碎在一起,执行lambda lifting将嵌套的闭包转换为普通的 lambdas 等等:
xs
使用-O2开关,即使对于此版本,GHC也会执行完全记忆。只要我们不忘记单形类型签名(或者它再次进行本地共享)。