我想编写一个函数,它接受一个列表并返回满足给定条件的所有可能子集的列表。 例如,我想拥有[1,2,3,4]的所有3个大小的子集,但没有任何包含2和3的子集。
$> func [1,2,3,4] 3
[[1,2,4],[1,3,4]]
要生成大小为N的所有子集,我有以下函数(找到here):
kCombinations :: [a] -> Integer -> [[a]]
kCombinations _ 0 = [[]]
kCombinations [] _ = []
kCombinations (x:xs) k = withHead ++ withoutHead
where withHead = map (x:) (kCombinations xs (k-1))
withoutHead = kCombinations xs k
我知道对我的问题最简单的解决方案是首先生成所有组合,然后使用过滤功能。但是对于像kCombinations [1..30] 6
这样的大问题,需要很长时间才能完成。
您能否告诉我如何在生成所有组合时过滤掉一些数据?
答案 0 :(得分:2)
这个问题很多,各种方法的最新比较可以在这里找到:Comparison of techniques for generating combinations
由于记忆,提到的最后一个方法(subsequencesOfSize
)非常高效。我的机器与ghci的时序比较:
length $ kCombinations [1..30] 6 - time: 2.73 secs
length $ subsequencesOfSize 6 [1..30] - time: 0.40 secs
要解决原始问题(没有2和3的子集),基本上有两种计算答案的方法:
-- import Data.List
answer1 = filter (\s -> not (elem 2 s && elem 3 s)) $ subsequencesOfSize 6 [1..30]
answer2 = map (2:) subs23 ++ map (3:) subs23 ++ subsequencesOfSize 6 nums'
where nums = [1..30]
nums' = [1..30] \\ [2,3]
subs23 = subsequencesOfSize 5 nums'
我的盒子上的时间(再次ghci):
length answer1 -- 1.48 secs
length answer2 -- 0.36 secs
answer1
显然是天真的做法; answer2
应用了一些基本集理论,并且很容易推广到计算不包含任何两个数字的子集 - 您可以决定它是否合法。
答案 1 :(得分:2)
可以改进用户5402提到的subsequencesOfSize
功能。比较this和this。这是因为第二版中为(l-n)
,因此subsequencesOfSize 3 [1..350]
等于subsequencesBySize [1..350] !! 347
,因此会创建大量未使用的列表。
当具有p
为真的属性的谓词p xs
过滤子序列意味着all p (inits xs)
为真时,可以将谓词集成到子序列的生成中以获得更大的结果效率。对于谓词"不包含2和3"
这就是你想要的:
zapWith f xs [] = xs
zapWith f (x:xs) (y:ys) = f x y : zapWith f xs ys
filterCombs :: ([a] -> Bool) -> Int -> [a] -> [[a]]
filterCombs p n xs | n > length xs = []
filterCombs p n xs = go xs id !! n where
go [] ds = [[[]]]
go (x:xs) ds
| p (ds' []) = zapWith (++) ([] : map (map (x:)) with) without
| otherwise = without
where
ds' = ds . (x:)
with = go xs ds'
without = go xs ds
zapWith
故意不是详尽无遗的。 ds
函数中的go
是一个差异列表,其中包含所有先前的元素。您可以这样阅读go
:如果属性p
适用于<all previous elements of xs> ++ [x]
,则包含x
和x
的组合,否则仅包含x
}}
一些例子:
conseq (x:y:xs) = succ x == y && conseq (y:xs)
conseq xs = True
main = do
print $ filterCombs (\xs -> any (`notElem` xs) [2,3]) 3 [1..5]
print $ filterCombs conseq 4 $ [1..8] ++ [8,7..1]
print $ filterCombs (all (<= 10)) 9 [1..5000]
打印
[[1,2,4],[1,2,5],[1,3,4],[1,3,5],[1,4,5],[2,4,5],[3,4,5]]
[[1,2,3,4],[1,2,3,4],[2,3,4,5],[2,3,4,5],[3,4,5,6],[3,4,5,6],[4,5,6,7],[4,5,6,7],[5,6,7,8],[5,6,7,8]]
[[1,2,3,4,5,6,7,8,9],[1,2,3,4,5,6,7,8,10],[1,2,3,4,5,6,7,9,10],[1,2,3,4,5,6,8,9,10],[1,2,3,4,5,7,8,9,10],[1,2,3,4,6,7,8,9,10],[1,2,3,5,6,7,8,9,10],[1,2,4,5,6,7,8,9,10],[1,3,4,5,6,7,8,9,10],[2,3,4,5,6,7,8,9,10]]