递归函数的惰性模式

Lazy mode of recursive function

我要找到解决这个问题的办法:

将给定列表拆分为具有给定子列表长度和跳过长度的子列表,例如:

groupEvery 3 1 [1..6] = [[1,2,3], [2,3,4], [3,4,5], [4,5,6]]
groupEvery 3 2 "abcdefghij" = [ "abc", "cde", "efg", "ghi", "ij"]

groupEvery n p l 参数详细信息是:

这是我的解决方案:

groupEvery :: Int -> Int -> [a] -> [[a]] -> [[a]]
groupEvery n p xs acc
    | length xs <= n = acc ++ [xs]
    | otherwise = groupEvery n p dropped (acc++[segment])
        where
            dropped = drop p xs
            segment = take n xs

但是这个解决方案并不偷懒,例如take 3 Lib2.groupEvery 3 1 [1..] []永远不会完成。 我的问题是关于

主要问题是您使用了累加器,并且在遍历整个输入列表时仅 return 列表。此外 length l 将遍历整个列表。你不需要计算长度。这也是低效的:如果列表很长,它每次都会生成一个线性 运行.

您可以在列表上进行模式匹配,对于空列表 return 空列表,对于 non-empty 列表产生 take n xs 并使用 drop p xs 递归。

groupEvery :: Int -> Int -> [a] -> [[a]]
groupEvery _ _ [] = []
groupEvery n p xs = take n xs : groupEvery n p (drop p xs)

因此产生:

ghci> take 3 (groupEvery 3 1 [1..])
[[1,2,3],[2,3,4],[3,4,5]]

您可以通过组合 take ndrop p 来进一步改进函数,这样您只遍历列表的 min n p 部分一次。我把它留作练习。

您在这里对“累加器”的使用含糊不清。

有用的术语是“状态传递和构建”、“输出列表创建”、“尾递归”和“保护递归”,在严格设置中也称为“尾递归模缺点”。

是的,我们可以通过受保护的递归逐渐创建输出列表,同时安排从一个调用到下一个调用的传递和累积/构建状态:

makeList elt next state = go state
  where
  go state = elt state : go (next state)
  --- which is just
  -- go = map elt . iterate next 

("guarded" 递归,由 lazy 数据构造函数保护,此处 :).

具体来说,你的例子是

groupEvery n p  =  makeList (take n) (drop p)

直到终止条件:

> takeWhile (not . null) $ groupEvery 3 2 [1..20]
[[1,2,3],[3,4,5],[5,6,7],[7,8,9],[9,10,11],[11,12,13],
 [13,14,15],[15,16,17],[17,18,19],[19,20]]