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
(这是一个正确的答案,顺便说一句)。这可以说更容易理解。因此,您的问题仅限于g
定义,
g (a,b) = let c = a ++ zipWith (+) b c in c
Haskell 的定义是递归的(它们等同于 Scheme 的letrec
,而不是let
)。
在这里它起作用了,因为当c
被延迟消费时,它的定义说它是由两部分组成的,a ++ ...
所以首先a
被消费。这有效,因为a
不依赖于c
. 计算a
不需要任何知识c
。
In 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 ++))