这个看起来很简单的haskell程序(动态编程)的解释



这是一个计算一美元分区方式的程序。我不理解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
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。计算CCD_ 10不需要任何关于CCD_ 11的知识。

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 ++))

y = f y等价于应用程序的无限链:`y=f(f(f)…

因此CCD_ 20等价于c=a++(zipWith(+)b(a++(zipWith(+)b(…)))

这是递归定义的一个特别复杂的使用。"change"one_answers"c"都是根据它们自己来定义的。

"change_"是一个无限长的Integer单链列表。

这个"c"也是一个无限长的Integer单链列表。

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

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

因此,"c"的第一个元素取自"a"。然后,一旦这些元素用完,"c"的以下元素就来自于"zipWith(+)bc"。

现在,"b"one_answers"c"是无限长的Integer单链表。

"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) ++...

最新更新