【问题标题】:Defining inits function recursively递归定义init函数
【发布时间】:2025-12-16 05:05:02
【问题描述】:

在 Data.List 模块中有一个 inits 函数,例如,[1,2,3,4] -> [[],[1],[1,2],[1,2,3],[ 1,2,3,4]]

我正在尝试使用递归定义类似的函数,但是我想不出一种按正确顺序执行的方法。我得到的最接近的是向后的列表,result = [[],[4],[3,4],[2,3,4],[1,2,3,4]]:

inits' :: [Int] -> [[Int]]
inits' [] = [[]]
inits' (x:xs) = inits' xs ++ [(x:xs)]

我不确定如何通过按正确顺序一次添加一个元素来创建列表?有人可以指出正确的方向,还是不能通过递归来做?

【问题讨论】:

  • 提示:您可以使用差异列表。
  • @WillemVanOnsem,这在大多数情况下都是一个很好的方法,但即使在奇怪的情况下也能获得最佳性能,最好使用适当的队列。
  • 特别是,强制整个结果应该是 O(n^2),但到达任何结果列表的任何元素理想情况下应该是 O(i+k)(即到该元素的距离) .同时完成这两个似乎需要一个持久队列。
  • 这个question 似乎解决了inits 的有效版本。
  • 另一方面,我忘记了优化的简单方法(大 O,但具有平庸的常数因子):使用 take。但这对于这个问题可能还不够“递归”。

标签: list haskell recursion


【解决方案1】:

尝试这样的函数最简单的方法就是查看所需的结果并在函数方程的 RHS 上进行“反向模式匹配”

你已经拥有了

inits' [] = [[]]

现在有了inits (x:xs),例如inits (1:[2,3,4]),您知道结果应该是[[],[1],[1,2],[1,2,3],[1,2,3,4]],它与模式[]:_ 匹配。所以

inits' (x:xs) = [] : _

现在,最简单的递归就是在xs 上再次调用inits',就像

inits' (x:xs) = [] : inits' xs

然而,这并没有给出正确的结果:假设递归调用正常工作,你有

inits' (1:[2,3,4]) = [] : [[],[2],[2,3],[2,3,4]]
                   = [[],[],[2],[2,3],[2,3,4]]

1 完全丢失了,显然,因为我们实际上并没有在定义中使用它。我们需要使用它,实际上它应该放在递归结果中的所有列表块之前。你可以通过map 做到这一点。

【讨论】:

    【解决方案2】:

    我们可以将所有剩余的inits的数据添加到前面,例如:

    inits' :: [a] -> [[a]]
    inits' [] = [[]]
    inits' (x:xs) = [] : map (x:) (inits' xs)

    作为基本情况,当输入为空列表时,我们返回一个带有空列表的单例列表。

    在递归的情况下,我们首先产生一个空列表,然后是列表尾部的inits',但是所有这些元素前面都带有x(带有map (x:) )。

    那么我们有:

    Prelude> inits' [1,4,2,5]
    [[],[1],[1,4],[1,4,2],[1,4,2,5]]
    

    因为(不按评估顺序):

       inits' [1,4,2,5]
    -> [] : map (1:) (inits' [4,2,5])
    -> [] : map (1:) ([] : map (4:) (inits' [2,5]))
    -> [] : map (1:) ([] : map (4:) ([] : map (2:) (inits' [5])))
    -> [] : map (1:) ([] : map (4:) ([] : map (2:) ([] : map (5:) (inits' []))))
    -> [] : map (1:) ([] : map (4:) ([] : map (2:) ([] : map (5:) [[]])))
    -> [] : map (1:) ([] : map (4:) ([] : map (2:) ([] : [[5]])))
    -> [] : map (1:) ([] : map (4:) ([] : map (2:) [[],[5]]))
    -> [] : map (1:) ([] : map (4:) ([] : [[2],[2,5]]))
    -> [] : map (1:) ([] : map (4:) [[],[2],[2,5]])
    -> [] : map (1:) ([] : [[4],[4,2],[4,2,5]])
    -> [] : map (1:) [[],[4],[4,2],[4,2,5]]
    -> [] : [[1],[1,4],[1,4,2],[1,4,2,5]]
    -> [[],[1],[1,4],[1,4,2],[1,4,2,5]]
    

    【讨论】:

    • 这个定义看起来完全等同于"horrifyingly inefficient" 一个from ghc 7.8.3。在 ghci 提示符下快速测试 head $ inits' [1..] !! n,经过解释,确实揭示了 ~n^2 个经验增长顺序。
    【解决方案3】:

    我认为你应该改变你的函数定义:

    inits' :: [Int] -> [[Int]]
    

    到:

    inits' :: [a] -> [[a]]
    

    因为来自Data.Listinits[a] -> [[a]] 类型,它并不关心列表中的实际内容。它需要是多态的并接受任何类型的列表。

    此外,由于其他人已经展示了最直接的递归方法,您也可以在此处使用foldr

    这是基本代码:

    inits' :: [a] -> [[a]]
    inits' = foldr (\x acc -> [] : (map (x:) acc)) [[]]
    

    [[]] 是基本情况,就像在您的函数中一样。对于实际的递归部分,它是如何与调用 inits' [1, 2, 3, 4] 一起工作的:

    • 在值4 处从右侧开始折叠,并创建[[], [4]]
    • 现在价值3,并创建[[], [3], [3, 4]
    • 现在价值2,并创建[[], [2], [2, 3], [2, 3, 4]]
    • 现在价值1,并创建[[], [1], [1, 2], [1, 2, 3], [1, 2, 3, 4]]

    这给出了所需的最终嵌套列表,类似于函数调用:

    *Main> inits' [1,2,3,4]
    [[],[1],[1,2],[1,2,3],[1,2,3,4]]
    

    根据上述行为,您只需关注[] : (map (x:) acc),将当前值x 映射到累积列表acc 中,同时在每个折叠前添加一个空列表。

    如果您仍然无法理解foldr,您可以查看这个从右侧折叠的最小示例:

    foldr f x [a, b, c] = a `f` (b `f` (c `f` x))
    

    How does foldr work?

    【讨论】: