以下(非最佳)代码为特定子集生成大小为N的所有子集。
这段代码可行,但正如我所说,这是非常不理想的。使用中间列表来避免Set.insert的O(log(n))似乎没有帮助,因为后来将列表重新转换为Set
的成本很高有人可以建议如何优化代码吗?
import qualified Data.Set as Set
subsetsOfSizeN :: Ord a => Int -> Set.Set a -> Set.Set (Set.Set a)
subsetsOfSizeN n s
| Set.size s < n || n < 0 = error "subsetOfSizeN: wrong parameters"
| otherwise = doSubsetsOfSizeN n s
where doSubsetsOfSizeN n s
| n == 0 = Set.singleton Set.empty
| Set.size s == n = Set.singleton s
| otherwise =
case Set.minView s of
Nothing -> Set.empty
Just (firstS, restS) ->
let partialN n = doSubsetsOfSizeN n restS in
Set.map (Set.insert firstS) (partialN (n-1)) `Set.union` partialN n
答案 0 :(得分:14)
这是受Pascal三角形的启发。
choose :: [b] -> Int -> [[b]]
_ `choose` 0 = [[]]
[] `choose` _ = []
(x:xs) `choose` k = (x:) `fmap` (xs `choose` (k-1)) ++ xs `choose` k
答案 1 :(得分:6)
这段代码可行,但正如我所说,这是非常不理想的。
对我来说似乎并不那么糟糕。大小为k
的{{1}}大小n
的子集数量为n `choose` k
,k ~ n/2
的增长速度相当快。因此,创建所有子集必须严重缩放。
使用中间列表来避免
O(log(n))
Set.insert
似乎没有帮助,因为稍后将列表重新转换为Set会产生大量费用。
嗯,我发现使用列表可以提供更好的性能。我认为,这不是渐近的,而是一个不可忽略的或多或少的常数因素。
但首先,代码中的效率低下很容易修复:
Set.map (Set.insert firstS) (partialN (n-1))
请注意Set.map
必须从头开始重建树。但我们知道firstS
始终小于partialN (n-1)
中任何集合中的任何元素,因此我们可以使用可以重用集合主干的Set.mapMonotonic
。
这个原则也是使列表具有吸引力的原因,子集是按字典顺序生成的,因此我们可以使用效率更高的Set.fromList
代替Set.fromDistinctAscList
。转录算法产生
onlyLists :: Ord a => Int -> Set.Set a -> Set.Set (Set.Set a)
onlyLists n s
| n == 0 = Set.singleton Set.empty
| Set.size s < n || n < 0 = error "onlyLists: out of range n"
| Set.size s == n = Set.singleton s
| otherwise = Set.fromDistinctAscList . map Set.fromDistinctAscList $
go n (Set.size s) (Set.toList s)
where
go 1 _ xs = map return xs
go k l (x:xs)
| k == l = [x:xs]
| otherwise = map (x:) (go (k-1) (l-1) xs) ++ go k (l-1) xs
在我运行的几个基准测试中,比使用Set
s的修正算法快1.5到2倍。
反过来,在我的标准基准测试中,这几乎是dave4420的两倍。
答案 2 :(得分:1)
subsets :: Int -> [a] -> [[a]]
subsets 0 _ = [[]]
subsets _ [] = []
subsets k (x:xs) = map (x:) (subsets (k - 1) xs) ++ subsets k xs
答案 3 :(得分:0)
首先,使用更好的算法。
看看你的最后一行:
Set.map (Set.insert firstS) (partialN (n-1)) `Set.union` partialN n
评估doSubsetsOfSizeN k (Set.fromList $ 1:2:xs)
将涉及评估doSubsetsOfSizeN (k-1) (Set.fromList xs)
两次(一次插入1
时,一次插入2
)。这种重复是浪费。
输入更好的算法。
mine :: Ord a => Int -> Set.Set a -> Set.Set (Set.Set a)
mine n s | Set.size s < n || n < 0 = Set.empty
| otherwise = Set.foldr cons nil s !! n
where
nil :: Ord a => [Set.Set (Set.Set a)]
nil = Set.singleton Set.empty : repeat Set.empty
cons :: Ord a => a -> [Set.Set (Set.Set a)] -> [Set.Set (Set.Set a)]
cons x sets = zipWith Set.union sets
(Set.empty : map (Set.map $ Set.insert x) sets)
mine 9 (Data.Set.fromList [0..18]) `seq` ()
比subsetsOfSizeN 9 (Data.Set.fromList [0..18]) `seq` ()
快,并且应该具有更好的渐近性能。
我还没有尝试进一步优化它。可能还有更好的算法。
(如果insert
和fromList
的费用有问题,您应该考虑回馈列表而不是一组广告。)
答案 4 :(得分:0)
我发现了这一点,可能对你有帮助
f [] = [[1]]
f l = (:) [u] l'
where
u = succ (head (head l))
l' = (++) l (map(\x->(:) u x) l)
fix f n = if (n==0) then [] else f (fix f (n-1))
测试它
$ length $ (fix f 10) => 1023 -- The empty set is always include then == 1024