Haskell 中的惰性加泰罗尼亚数字
Lazy Catalan Numbers in Haskell
我怎样才能有效地生成无限的加泰罗尼亚数字列表?我现在的工作速度相当快,但在我看来应该有更好的方法。
c 1 = 1
c n = sum (zipWith (*) xs (reverse xs)) : xs
where xs = c (n-1)
catalan = map (head . c) [1..]
我尝试改用 fix
,但是 lambda 不够懒惰,无法终止计算:
catalan = fix (\xs -> xs ++ [zipWith (*) xs (reverse xs)])
我意识到 (++)
并不理想
有没有更好的方法?该功能可以足够懒惰吗?我知道第 nth 有一个明确的公式,但我宁愿避免使用它。
Catalan numbers [wiki] 可以归纳定义为:
C0 = 1 和 Cn+1= (4n+2)×Cn/(n+2).
所以我们可以这样实现:
catalan :: Integral i => [i]
catalan = xs
where xs = 1 : zipWith f [0..] xs
f n cn = div ((4*n+2) * cn) (n+2)
例如:
Prelude> take 10 catalan
[1,1,2,5,14,42,132,429,1430,4862]
我猜您正在寻找一个惰性的、无限的、自引用的列表,其中包含所有使用基本递归关系之一的加泰罗尼亚数字。毕竟,这对斐波那契数列来说是很常见的事情。但是,如果您想要特定问题的答案,那么指定您所指的递归关系会有所帮助。我猜这就是你的意思:
cat :: Integer -> Integer
cat 1 = 1
cat n = sum [ cat i * cat (n - i) | i <- [1 .. n - 1] ]
如果是这样,转换为自引用形式如下所示:
import Data.List (inits)
cats :: [Integer]
cats = 1 : [ sum (zipWith (*) pre (reverse pre)) | pre <- tail (inits cats) ]
这比斐波那契示例复杂得多,因为递归指的是列表中所有以前的条目,而不仅仅是最近的固定的少量条目。使用 Data.List
中的 inits
是在每个位置获取前缀的最简单方法。我在那里使用 tail
因为它的第一个结果是空列表,这在这里没有帮助。其余的是对递归关系的直接重写,我没有太多要说的。除了...
它的表现会很糟糕。我的意思是,它比我的 cat
函数的指数递归调用要好,但是有很多列表操作在分配然后丢弃大量内存单元。该递归关系不太适合列表数据类型的递归结构。你可以探索很多方法来提高效率,但它们最终都会很糟糕。对于这种特殊情况,如果您想要性能,则采用封闭形式的解决方案是可行的方法。
,你想要的是
> cats = 1 : unfoldr (\ fx -> let x = sum $ zipWith (*) fx cats in Just (x, x:fx)) [1]
> take 10 cats
[1,1,2,5,14,42,132,429,1430,4862]
这避免了前缀的重复反转(如在链接的答案中),通过将状态展开为反转前缀,同时对状态进行转换并生成下一个元素。
我们不必维护非反向前缀,因为用加泰罗尼亚语列表本身压缩反向前缀会处理加泰罗尼亚语前缀的长度。
您确实说过要避免使用直接公式。
加泰罗尼亚数最好通过它们的母函数来理解,它满足关系
f(t) = 1 + t f(t)^2
这可以用Haskell表示为
f :: [Int]
f = 1 : convolve f f
convolve
的合适定义。分解出 convolve
很有帮助,因为许多其他计数问题都采用这种形式。例如,广义加泰罗尼亚数枚举三叉树,其生成函数满足关系
g(t) = 1 + t g(t)^3
可以用Haskell表示为
g :: [Int]
g = 1 : convolve g (convolve g g)
convolve
可以使用 Haskell 原语编写为
convolve :: [Int] -> [Int] -> [Int]
convolve xs = map (sum . zipWith (*) xs) . tail . scanl (flip (:)) []
对于这两个示例和许多其他特殊情况,有可以更快计算的公式。然而,convolve
更通用,认知效率更高。在一个典型的场景中,一个人已经根据其生成函数的多项式关系理解了一个计数问题,现在想要计算一些数字以便在 The On-Line Encyclopedia of Integer Sequences 中查找它们。一个人想要进出,对复杂性漠不关心。哪种语言最不麻烦?
如果有人看过斐波那契数列的标志性 Haskell 定义
fibs :: [Int]
fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
那么想像生成函数的乘积一定有类似的成语。那次搜索让我来到这里。
我怎样才能有效地生成无限的加泰罗尼亚数字列表?我现在的工作速度相当快,但在我看来应该有更好的方法。
c 1 = 1
c n = sum (zipWith (*) xs (reverse xs)) : xs
where xs = c (n-1)
catalan = map (head . c) [1..]
我尝试改用 fix
,但是 lambda 不够懒惰,无法终止计算:
catalan = fix (\xs -> xs ++ [zipWith (*) xs (reverse xs)])
我意识到 (++)
并不理想
有没有更好的方法?该功能可以足够懒惰吗?我知道第 nth 有一个明确的公式,但我宁愿避免使用它。
Catalan numbers [wiki] 可以归纳定义为:
C0 = 1 和 Cn+1= (4n+2)×Cn/(n+2).
所以我们可以这样实现:
catalan :: Integral i => [i]
catalan = xs
where xs = 1 : zipWith f [0..] xs
f n cn = div ((4*n+2) * cn) (n+2)
例如:
Prelude> take 10 catalan
[1,1,2,5,14,42,132,429,1430,4862]
我猜您正在寻找一个惰性的、无限的、自引用的列表,其中包含所有使用基本递归关系之一的加泰罗尼亚数字。毕竟,这对斐波那契数列来说是很常见的事情。但是,如果您想要特定问题的答案,那么指定您所指的递归关系会有所帮助。我猜这就是你的意思:
cat :: Integer -> Integer
cat 1 = 1
cat n = sum [ cat i * cat (n - i) | i <- [1 .. n - 1] ]
如果是这样,转换为自引用形式如下所示:
import Data.List (inits)
cats :: [Integer]
cats = 1 : [ sum (zipWith (*) pre (reverse pre)) | pre <- tail (inits cats) ]
这比斐波那契示例复杂得多,因为递归指的是列表中所有以前的条目,而不仅仅是最近的固定的少量条目。使用 Data.List
中的 inits
是在每个位置获取前缀的最简单方法。我在那里使用 tail
因为它的第一个结果是空列表,这在这里没有帮助。其余的是对递归关系的直接重写,我没有太多要说的。除了...
它的表现会很糟糕。我的意思是,它比我的 cat
函数的指数递归调用要好,但是有很多列表操作在分配然后丢弃大量内存单元。该递归关系不太适合列表数据类型的递归结构。你可以探索很多方法来提高效率,但它们最终都会很糟糕。对于这种特殊情况,如果您想要性能,则采用封闭形式的解决方案是可行的方法。
> cats = 1 : unfoldr (\ fx -> let x = sum $ zipWith (*) fx cats in Just (x, x:fx)) [1]
> take 10 cats
[1,1,2,5,14,42,132,429,1430,4862]
这避免了前缀的重复反转(如在链接的答案中),通过将状态展开为反转前缀,同时对状态进行转换并生成下一个元素。
我们不必维护非反向前缀,因为用加泰罗尼亚语列表本身压缩反向前缀会处理加泰罗尼亚语前缀的长度。
您确实说过要避免使用直接公式。
加泰罗尼亚数最好通过它们的母函数来理解,它满足关系
f(t) = 1 + t f(t)^2
这可以用Haskell表示为
f :: [Int]
f = 1 : convolve f f
convolve
的合适定义。分解出 convolve
很有帮助,因为许多其他计数问题都采用这种形式。例如,广义加泰罗尼亚数枚举三叉树,其生成函数满足关系
g(t) = 1 + t g(t)^3
可以用Haskell表示为
g :: [Int]
g = 1 : convolve g (convolve g g)
convolve
可以使用 Haskell 原语编写为
convolve :: [Int] -> [Int] -> [Int]
convolve xs = map (sum . zipWith (*) xs) . tail . scanl (flip (:)) []
对于这两个示例和许多其他特殊情况,有可以更快计算的公式。然而,convolve
更通用,认知效率更高。在一个典型的场景中,一个人已经根据其生成函数的多项式关系理解了一个计数问题,现在想要计算一些数字以便在 The On-Line Encyclopedia of Integer Sequences 中查找它们。一个人想要进出,对复杂性漠不关心。哪种语言最不麻烦?
如果有人看过斐波那契数列的标志性 Haskell 定义
fibs :: [Int]
fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
那么想像生成函数的乘积一定有类似的成语。那次搜索让我来到这里。