这个简单的haskell程序(动态编程)的解释

时间:2013-08-11 11:45:10

标签: haskell dynamic-programming

这是一个计算分割一美元的方法的程序。我不理解行c = a ++ zipWith (+) b c,因为在此行之前未声明此行c,那么我们如何压缩b和c? (我是哈斯克尔的新手,很赞赏一个很好的解释)

import Data.List
change [] = 1 : repeat 0
change (d : ds) = c where
  (a, b) = splitAt d (change ds)
  c = a ++ zipWith (+) b c
result = change [1, 5, 10, 15, 20, 25, 50] !! 100

3 个答案:

答案 0 :(得分:3)

change [] = 1 : repeat 0
change (d : ds) = c where
  (a, b) = splitAt d (change ds)
  c = a ++ zipWith (+) b c

然后,

result = (!! 100) $ xs 
  where 
    xs = change [1, 5, 10, 15, 20, 25, 50] 
       = let -- g = (\(a,b)-> fix ((a++) . zipWith (+) b)) 
             g (a,b) = let c = a ++ zipWith (+) b c in c
         in 
           g . splitAt 1 . change $ [5, 10, 15, 20, 25, 50]
         = g . splitAt 1 .
           g . splitAt 5 . change $ [10, 15, 20, 25, 50]
         = ....
         = let h n = g . splitAt n 
           in
             h 1 . h 5 . h 10 . h 15 . h 20 . h 25 . h 50 . (1:) . repeat $ 0

或更简单,

Prelude> (!! 100) $ foldr h (1:cycle [0]) [1, 5, 10, 15, 20, 25, 50]
1239

(这是一个正确答案,BTW)。这可以说更容易理解。因此,您的问题已本地化为g定义

    g (a,b) = let c = a ++ zipWith (+) b c in c

关于Haskell定义的事情是它们递归(它们等同于Scheme的letrec,而不是let)。

这里有效,因为当c 懒惰消耗时,它的定义表明它是由两部分a ++ ...构建的,因此消耗了第一个a。这是有效的,因为a不依赖于c。计算a不需要任何c的知识。

zipWith (+) b c中,c基本上是 指针 到正在定义的序列中,length a陷阱 rest返回

    g (a,b) = 
      let c = a ++ rest
          rest = zipWith (+) b c
      in c

我们有h n xs = g (splitAt n xs),这就是描述输入列表与结果的总和,向前移动n个切口:

    x1 x2 x3 x4 x5 x6 x7 x8 ................ xs     A
                   y1 y2 y3 y4 y5 .......... ys     B
    --------
    y1 y2 y3 y4 y5 y6 y7.................... ys == A + B

这表明可以使用改进的访问位置重写h

change ds n = foldr h (1:cycle [0]) ds !! n  -- [1, 5, 10, 15, 20, 25, 50] 100
  where
    h n xs = ys where ys = zipWith (+) xs (replicate n 0 ++ ys)
        -- = fix (zipWith (+) xs . (replicate n 0 ++))

答案 1 :(得分:2)

y = f y相当于无限的应用链:`y = f(f(f(f(...

所以c = a ++ (zipWith (+) b c)相当于c = a ++(zipWith(+)b(a ++(zipWith(+)b(...)))

答案 2 :(得分:2)

这是递归定义的特别复杂的用法。 “改变”和“c”都是根据自身定义的。

'change _'是一个无限长的单链整数列表。

这个'c'也是无限长的单一链接的整数列表。

'a ++ ...'的第一个元素是什么?如果'a'不是空的(这里它不是空的,因为列表传递给更改都是正的)那么它就是'a'的第一个元素。

实际上'a'在第一次更改中的长度为'1',然后是'5'然后是'10',直到最后'a'的长度为'50'。

所以'c'的第一个元素取自'a'。然后,一旦用完,'c'的以下元素来自'zipWith(+)b c'。

现在'b'和'c'是无限长的单链整数列表。

'b'的第一个元素来自'a'部分之后'change _'的递归调用的一部分。 'c'的第一部分是'a'部分。

将'a'部分的长度设为5,并按名称'p1'调用'a'。

c = (5 elements of 'a', call this p1)
  ++ (5 elements of zipWith (+) p1 b, call this p2)
  ++ (5 elements of zipWith (+) p2 (drop 5 b), call this p3)
  ++ (5 elements of zipWith (+) p3 (drop 10 b) ++...