记忆帕斯卡三角形

时间:2012-07-13 15:04:02

标签: haskell ghci memoization pascals-triangle

我对解决问题的实际解决方案或其他方法不感兴趣,这是我需要帮助的备忘录:)

我需要帮助用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)

3 个答案:

答案 0 :(得分:2)

Memoization在您的函数中不起作用,因为像memopascals 5 5之类的调用在内部构建三角形的一部分并从中返回单个值。另一个电话mempascals 6 6memopascals 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定义依赖关于rc的值,但您可以调用“外部”函数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也会执行完全记忆。只要我们不忘记单形类型签名(或者它再次进行本地共享)。