所有大小为N的子集满足Haskell中的条件

时间:2014-12-14 22:21:22

标签: algorithm haskell optimization complexity-theory

我想编写一个函数,它接受一个列表并返回满足给定条件的所有可能子集的列表。 例如,我想拥有[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这样的大问题,需要很长时间才能完成。

您能否告诉我如何在生成所有组合时过滤掉一些数据?

2 个答案:

答案 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功能。比较thisthis。这是因为第二版中为(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],则包含xx的组合,否则仅包含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]]