如何提高这个Haskell代码的性能?

时间:2012-05-11 18:53:52

标签: haskell garbage-collection set parallel-processing lazy-evaluation

我遇到了以下问题:

从初始集[1,2,3,4]计算所有可能的子集,即[[1],[2],[3],[4],[1,2],[1,3],[1,4],[2,3],[2,4],[3,4],[1,2,3],[1,2,4],[1,3,4],[2,3,4],[1,2,3,4]]

我编写了以下Haskell程序generate.hs,这是正确的。

generateSets :: Eq a => [a] -> [[a]] -> [[a]] -> [[a]]
generateSets []  _  _  = []
generateSets src [] _  = let isets = growthup [] src in generateSets src iset iset
generateSets src sets rsets = if null sets' then rsets else generateSets src sets' (rsets++sets')
  where sets' = concatMap (flip growthup src) sets

growthup :: (Eq a) => [a] -> [a] -> [[a]]
growthup ps ss = map (\suf -> ps++[suf]) ss'
  where ss' = nextoccurence ps ss

nextoccurence :: (Eq a) => [a] -> [a] -> [a]
nextoccurence [] ys = ys
nextoccurence xs ys = tail ys'
  where ys' = dropWhile (/= last xs) ys

在GHC解释器中执行它 ghci ...

ghci> generate [1,2,3,4] [] []
ghci> [[1],[2],[3],[4],[1,2],[1,3],[1,4],[2,3],[2,4],[3,4],[1,2,3],[1,2,4],[1,3,4],[2,3,4],[1,2,3,4]]

每件事情都很顺利,但程序花费的时间太长,例如只有30件小套装。

我的问题是:有可能改进我的代码,以便从haskell懒惰,或garbagge收藏家或其他东西中获得更多?

我的代码是否适合并行化?

感谢您的回复!

1 个答案:

答案 0 :(得分:7)

集合包含批次子集。实际上,一组 n 元素具有 2 n 子集,因此一组30个元素具有超过十亿个子集。无论您使用哪种方法生成它们,即使迭代结果也需要很长时间。对于较大的套装,你几乎可以忘记在宇宙热死之前经历它们。

因此,只有在性能方面才能做到这一点,因为即使将算法的速度提高一倍,也只能让你同时处理一个元素的列表。 对于大多数应用程序,真正的解决方案是避免首先枚举所有子集。

也就是说,有一种简单的归纳方式来思考子集,这使得定义一个合适的子集函数变得容易,而不必进行任何相等的比较,这解决了实现中的一些问题。

对于基本情况,空集有一个子集:空集。

subsets [] = [[]]

对于具有至少一个元素(x:xs)的集合,我们有包含该元素的子集,而不包含该元素的子集。我们可以通过递归调用x来获取不包含subsets xs的子集,我们可以通过将x添加到这些子集来获得其余部分。

subsets (x:xs) = subsets xs ++ map (x:) (subsets xs)

Data.Listsubsequences的定义遵循相同的原则,但采用稍微优化的方式,也以不同的顺序返回子集,更好地利用共享。但是,正如我所说,列举长度为30的列表的子集无论如何都会变慢,最好的办法就是尽量避免在第一时间这样做。