生成集的子集。懒惰?

时间:2016-03-19 11:20:12

标签: haskell

我编写了一个生成子集子集的函数。当我按照以下方式subsets [1..]使用时,它会导致堆栈溢出。当涉及到“普通”(非懒惰)语言时,这是“正常”的行为。现在,我想改善我的懒惰功能。

P.S。我不懂懒惰(我试着理解它)所以也许我的问题对你来说很奇怪 - 请解释一下。 :)

P.S。 2随意在Haskell上告诉我一些关于我残疾的事情;)

subsets :: [a] -> [[a]]
subsets (x:xs) = (map (\ e -> x:e) (subsets xs)) ++ (subsets xs)
subsets [] = [[]]

3 个答案:

答案 0 :(得分:4)

该功能存在两个问题。首先,它会递归两次,这使得它比必要的指数更加无效(如果我们忽略指数的结果......),因为每次重新计算所有重叠子集的每个子树;这可以通过let递归调用来修复相同的值:

subsets' :: [a] -> [[a]]
subsets' [] = [[]]
subsets' (x:xs) = let s = subsets' xs
                  in map (x:) s ++ s

这已经允许您在几秒钟内计算length $ subsets' [1..25],而length $ subsets [1..25]需要......好吧,我没有等待;)

另一个问题是,对于您的版本,当您为其提供无限列表时,它将首先递归到该列表的无限尾。要以有意义的方式生成所有有限子集,我们需要确保两件事:首先,我们必须从较小的集合构建每个集合(以确保终止),其次,我们应该确保公平顺序(即,不首先生成列表[[1], [2], ...],永远不会生成其余的列表)。为此,我们从[[]]开始,递归地将当前元素添加到我们已生成的所有内容中,然后记住下一步的新列表:

subsets'' :: [a] -> [[a]]
subsets'' l = [[]] ++ subs [[]] l
  where subs previous (x:xs) = let next = map (x:) previous
                               in next ++ subs (previous ++ next) xs
        subs _ [] = []

这导致此顺序:

*Main> take 100 $ subsets'' [1..]
[[],[1],[2],[2,1],[3],[3,1],[3,2],[3,2,1],[4],[4,1],[4,2],[4,2,1],[4,3],[4,3,1],[4,3,2],[4,3,2,1],[5],[5,1],[5,2],[5,2,1],[5,3],[5,3,1],[5,3,2],[5,3,2,1],[5,4],[5,4,1],[5,4,2],[5,4,2,1],[5,4,3],[5,4,3,1],[5,4,3,2],[5,4,3,2,1],[6],[6,1],[6,2],[6,2,1],[6,3],[6,3,1],[6,3,2],[6,3,2,1],[6,4],[6,4,1],[6,4,2],[6,4,2,1],[6,4,3],[6,4,3,1],[6,4,3,2],[6,4,3,2,1],[6,5],[6,5,1],[6,5,2],[6,5,2,1],[6,5,3],[6,5,3,1],[6,5,3,2],[6,5,3,2,1],[6,5,4],[6,5,4,1],[6,5,4,2],[6,5,4,2,1],[6,5,4,3],[6,5,4,3,1],[6,5,4,3,2],[6,5,4,3,2,1],[7],[7,1],[7,2],[7,2,1],[7,3],[7,3,1],[7,3,2],[7,3,2,1],[7,4],[7,4,1],[7,4,2],[7,4,2,1],[7,4,3],[7,4,3,1],[7,4,3,2],[7,4,3,2,1],[7,5],[7,5,1],[7,5,2],[7,5,2,1],[7,5,3],[7,5,3,1],[7,5,3,2],[7,5,3,2,1],[7,5,4],[7,5,4,1],[7,5,4,2],[7,5,4,2,1],[7,5,4,3],[7,5,4,3,1],[7,5,4,3,2],[7,5,4,3,2,1],[7,6],[7,6,1],[7,6,2],[7,6,2,1]]

答案 1 :(得分:3)

您无法生成所有无限集的子集:它们形成一个不可数集。基数使它变得不可能。

最多,您可以尝试生成所有有限子集。为此,您无法从[]开始进行归纳,因为您永远不会到达[]。您需要从列表的开头进行归纳,而不是结束。

答案 2 :(得分:3)

正确的折叠解决方案是:

powerset :: Foldable t => t a -> [[a]]
powerset xs = []: foldr go (const []) xs [[]]
    where go x f a = let b = (x:) <$> a in b ++ f (a ++ b)

然后:

\> take 8 $ powerset [1..]
[[],[1],[2],[2,1],[3],[3,1],[3,2],[3,2,1]]