【问题标题】:Memoization pascals triangle记忆帕斯卡三角
【发布时间】:2012-07-13 11:02:51
【问题描述】:

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

我需要帮助来解决记忆化的帕斯卡三角问题。我想得到三角形底部的中间数字。 (欧拉计划 15)

第一个例子没有被记忆(虽然顾名思义)“20 20”不可解

第二次尝试是尝试做类似的事情:http://www.haskell.org/haskellwiki/Memoization

如果对某人来说更具可读性,第三个是关于 no2 的 hlints 建议。

我收到此错误,但我不确定它是否正确,即使它可以编译...(从 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)

【问题讨论】:

    标签: haskell ghci memoization pascals-triangle


    【解决方案1】:

    有几个实现记忆的指南(查看here 了解最近的一些讨论)。

    首先,在 GHC 编译器中使用 -O2 优化标志。其次,使用单态类型签名。命名您想要实现共享的中间列表。

    然后,注意您的嵌套定义。如果嵌套定义依赖于其封闭(“外部”)范围内的参数值,则意味着对该外部函数的每次调用都必须重新创建其所有嵌套定义,因此不会有任何 one 要共享的列表,但有许多单独的独立列表。

    在你的函数中,分离并命名你想要共享的列表表达式,我们得到

    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 的值,但您在嵌套函数 pascals 中调用“外部”函数 memopascals .每次调用memopascals创建自己的xs 副本,因为它依赖于memopascals 的参数rc。无法共享。

    如果您需要该依赖定义,则必须安排不要调用“超出范围”,而是留在该范围内,以便可以重用所有内部(“嵌套”)定义。

    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 的另一次调用将创建它自己的列表xs 内部定义的新副本,并将使用。我们可以称其为 “本地” 共享,而不是“全局”共享(即记忆化)。这意味着它运行速度很快,但使用相同参数的第二次调用与第一次调用的时间相同 - 而不是完全记忆化的 0 时间。

    只有一种方法可以实现,那就是让您的xs 定义独立。然后编译器可以将所有嵌套的范围框架粉碎在一起,执行lambda lifting将嵌套的闭包变成普通的lambdas等等:

    memopascals :: Int -> Int -> Integer
    memopascals r c = [[pascals a b | b<-[1..]] | a<-[1..]] !! (r-1) !! (c-1) 
     where 
       pascals 1 _ = 1 
       pascals _ 1 = 1 
       pascals x y = memopascals (x-1) y + memopascals x (y-1)
    

    使用 -O2 开关,即使对于这个版本,GHC 也会执行完整的记忆。只要我们不忘记单态类型签名(或者它又是本地共享)。

    【讨论】:

    • 很好的答案,易于理解,并为我提供了进一步调查的术语!竖起大拇指!
    • 不客气,谢谢!顺便说一句,它真的计算出帕斯卡三角形吗?我想那里一定是pascals x y = memopascals (x-1) y + memopascals (x-1) (y-1)
    • 是的,您可能是对的,我目前无法访问代码,但由于该值取决于上述两个,因此应该是这种情况。
    【解决方案2】:

    记忆在您的函数中不起作用,因为像 memopascals 5 5 这样的调用会在内部构建三角形的一部分并从中返回单个值。另一个调用 mempascals 6 6memopascals 5 5 内部的那个部分三角形没有任何联系。

    为了有效的记忆,您需要将公共部分(如表示三角形中计算出的数字的列表列表)放在一个单独的函数中,然后由访问特定索引的函数使用。这样您就可以使用相同的列表列表来查找不同的索引。

    通常最简单的方法是编写一个像 fullpascals :: [[Int]] 这样的函数来生成完整的(无限的)数据结构,然后编写另一个函数来访问该数据结构,比如

    memopascals x y = fullpascals !! (x-1) !! (y-1)
    

    在您的情况下,fullpascals 将与您当前的功能之一相同,但没有特定索引的参数。它甚至仍然可以在内部使用memopascals 进行递归。


    旁注: 在 wiki 的 memoized_fib 示例中,被记忆的“公共部分”不是直接所有 fib 的列表,而是访问所有 fib 列表的函数.效果是一样的,因为编译器足够聪明,可以对此进行优化。

    【讨论】:

      【解决方案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)
      

      这对错误无关紧要,但在最后一行,您想调用zmemopascals 而不是memopascals

      在第一行,您有两个列表索引运算符。所以zipWith pascals [1 .. ] [1 .. ] 必须有类型[[a]] 来表示一些apascals的定义说

      pascals :: Num t => Int -> Int -> t
      

      因此zipWith pascals [1 .. ] [1 .. ] :: [t]。因此类型推断找到t = [a],但编译器没有找到实例Num [a]

      对于记忆,您必须为完整的三角形命名,就像@sth 建议的那样,以避免重新计算(斐波那契记忆仅在编译器足够聪明时才起作用,它的形式无法保证)。

      另一种选择是使用iterate 构造三角形,

      pascal :: Int -> Int -> Integer
      pascal n k = iterate nextRow [1] !! n !! k
        where
          nextRow ks = zipWith (+) (0:ks) (ks ++ repeat 0)
      

      【讨论】:

      • 你能描述一下编译器在什么方面足够聪明吗?我怎样才能形成我的程序,以便编译器可以理解和优化?
      • 在斐波那契示例 memoized_fib = (map fib [0 ..] !!) 中,编译器注意到它可以共享列表 map fib [0 .. ] - 即使在不同的顶级调用之间也是如此。这并不完全是微不足道的。您可以通过给事物命名来更容易确定。根据经验,如果您想要共享某些内容,请为其命名以帮助编译器。
      • 谢谢,我想只是那一步让我感到困惑:)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-12-28
      • 1970-01-01
      • 2012-10-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-09-06
      相关资源
      最近更新 更多