Memoization pascals triangle

后端 未结 3 906
春和景丽
春和景丽 2020-12-20 07:55

I\'m not interested in the actual solution or other methods solving the problem, it\'s the memoization i need help with :)

I need help doing a pascals triangle probl

相关标签:
3条回答
  • 2020-12-20 08:26

    Memoization doesn't work in your functions because a call like memopascals 5 5 builds internally a part of the triangle and returns a single value from it. A different call mempascals 6 6 doesn't have any connection to that partial triangle internal to memopascals 5 5.

    For efficient memoization you want the common part (like the list of lists that represents the calculated numbers in the triangle) in a separate function, which is then used by the functions that access specific indexes. This way you use the same list of lists to lookup different indexes.

    Usually the easiest way to do this is to write one function like fullpascals :: [[Int]] that produces the complete (infinite) data structure, and than another function to access that data structure, like

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

    In your case fullpascals would be the same as one of your current functions, but without the parameters for a specific index. It can even still use memopascals internally for the recursion.


    Sidenote: In the memoized_fib example in the wiki, the "common part" that is memoized isn't directly the list of all fibs, but a function that accesses the list of all fibs. The effect is the same, since the compiler is smart enough to optimize this.

    0 讨论(0)
  • 2020-12-20 08:33
    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)
    

    Not that it matters for the error, but in the last line, you want to call zmemopascals rather than memopascals.

    In the first line, you have two list index operators. So zipWith pascals [1 .. ] [1 .. ] must have type [[a]] for some a. The definition of pascals says

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

    thus zipWith pascals [1 .. ] [1 .. ] :: [t]. Hence the type inference finds that t = [a], but the compiler doesn't find an instance Num [a].

    For memoisation, you have to give a name to the full triangle, like @sth suggested, to avoid recomputation (the Fibonacci memoisation only works because the compiler is smart enough, it's not guaranteed by its form).

    Another option would be to construct the triangle using iterate,

    pascal :: Int -> Int -> Integer
    pascal n k = iterate nextRow [1] !! n !! k
      where
        nextRow ks = zipWith (+) (0:ks) (ks ++ repeat 0)
    
    0 讨论(0)
  • 2020-12-20 08:35

    There are several guidelines to achieving memoization (take a look here for some recent discussion).

    First, use -O2 optimization flag with GHC compiler. Second, use monomorphic type signature. Name your intermediate list(s), that you want to achieve sharing of.

    Then, pay attention to your nested definitions. If a nested definition depends on value of an argument in its enclosing ("outer") scope, that means each call to that outer function will have to create all its nested definitions anew, so there won't be any one list to be shared, but many separate independent ones.

    Here in your function, separating out and naming the list expression that you want shared, we get

    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)
    

    Your xs definition is dependent on values of r and c, but you call your "outer" function, memopascals, from within a nested one, pascals. Each invocation of memopascals has to create its own copy of xs because it depends on memopascals's arguments, r and c. No sharing possible.

    If you need to have that dependent definition, you must arrange not to call "out of scope", but rather stay inside that scope, so that all the internal ("nested") definitions can be reused.

    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)
    

    Now each invocation of memopascals creates its internal definitions (from its nested scope) which then call each other, never calling out of scope - so the list xs gets reused, achieving sharing.

    But another call to memopascals will create its own new copy of its internal definition of the list xs, and will use it. We can call this a "local" sharing, not a "global" sharing (i.e. memoization). That means it runs fast, but second call with same parameters takes the same time as the first - not the 0 time as with full memoization.

    There's only one way to achieve that, and that is to make your xs definition independent. Then the compiler can smash all the nested scope frames together, perform lambda lifting turning nested closures into plain lambdas, etc. etc.:

    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)
    

    With -O2 switch, GHC performs full memoization even for this version. As long as we don't forget the monomorphic type signature (or it's local sharing again).

    0 讨论(0)
提交回复
热议问题