Haskell中实现带初始前缀的最终重复列表的方法问询
我明白你要的是先包含若干初始元素,之后进入无限重复模式的列表——就像22/7的小数展开那样,开头有个3,后面跟着无限循环的142857。这种结构在处理小数、连分数这类需要"前缀+循环体"的场景非常实用,而且完全可以适配fold这类操作,尤其是在惰性求值的语言(比如Haskell)里表现很好。
核心实现思路
其实逻辑很简单:把初始前缀列表和循环体的无限重复版本拼接起来就行。惰性语言里的列表是按需生成的,不会一次性生成无限元素,所以完全可以安全使用。
用Haskell的话,核心代码就是:
prefixedCycle :: [a] -> [a] -> [a] prefixedCycle prefix repeating = prefix ++ cycle repeating
对应你提到的22/7小数展开例子:
-- 22/7 = 3.(142857) decimal22Over7 :: [Int] decimal22Over7 = prefixedCycle [3] [1,4,2,8,5,7]
再举个实际例子,比如123/70的小数展开是1.7571428571428572...,前缀是[1,7],循环体是[5,7,1,4,2,8],实现起来就是:
decimal123Over70 :: [Int] decimal123Over70 = prefixedCycle [1,7] [5,7,1,4,2,8]
适配fold等操作的说明
在惰性求值环境下,foldr可以完美处理这种无限列表——因为它是从右往左、按需计算的,只要你的折叠函数不需要遍历整个无限列表就能得到结果(比如取前n个元素求和、找某个元素的位置等)。
比如用foldr结合take计算前5个元素的和:
takeSum :: Int -> [Int] -> Int takeSum n xs = foldr (+) 0 (take n xs) -- decimal22Over7前5个元素:3+1+4+2+8 = 18 takeSum 5 decimal22Over7 -- 结果为18
如果用foldl的话要注意,它是严格求值的,直接作用在无限列表上会陷入死循环,所以一定要配合take这类截断函数,确保只处理有限个元素。
扩展:连分数场景的应用
这种结构同样适配连分数的表示。比如√2的连分数是[1;(2)],也就是前缀为[1],循环体为[2],实现起来就是:
sqrt2ContinuedFraction :: [Int] sqrt2ContinuedFraction = prefixedCycle [1] [2]
你可以用这个列表配合fold操作逐步计算连分数的近似值:
-- 取前n项计算连分数近似值 approxContinuedFraction :: Int -> [Int] -> Rational approxContinuedFraction n = foldr (\x acc -> fromIntegral x + 1/acc) 0 . take n -- 取前5项:1 + 1/(2 + 1/(2 + 1/(2 + 1/2))) = 29/20 = 1.45 approxContinuedFraction 5 sqrt2ContinuedFraction -- 结果为29 % 20
总结
这种前缀列表 ++ cycle 循环体的模式既简单又高效,完全满足你对小数、连分数以及其他类似场景的需求,而且在惰性语言中能很好地和fold等高阶函数配合使用——只要记得根据折叠函数的严格性来决定是否需要截断列表。
内容的提问来源于stack exchange,提问作者pdmclean

