【问题标题】:All the subsets of size N which satisfy a condition in Haskell满足 Haskell 条件的所有大小为 N 的子集
【发布时间】:2014-12-14 22:21:22
【问题描述】:

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

您能告诉我如何在生成所有组合的过程中过滤掉一些数据吗?

【问题讨论】:

  • 您是在问如何在少于 O(2^n) 的时间内计算出 O(2^n) 的值吗?
  • 您的病情如何?它是否允许仅基于其中的几个元素(如您的示例)排除某些子集?
  • 30 choose 6593775,所以我预计如果不只是打印出来(如果您正在打印列表),它需要一些时间。在 GHCi 中,我的计算机需要大约 2 秒来计算它,而打印它需要更长的时间(我没有等待它完成)。您可能可以使用差异列表对其进行一些改进,因为您经常追加,特别是因为它看起来很多都会与左侧相关联。

标签: algorithm haskell optimization complexity-theory


【解决方案1】:

可以改进 user5402 提到的subsequencesOfSize 功能。比较 thisthis。这是因为第二个版本中(l-n),所以subsequencesOfSize 3 [1..350]等于subsequencesBySize [1..350] !! 347,所以创建了很多未使用的列表。

当通过具有p xs 为真意味着all p (inits xs) 为真的属性的谓词p 过滤子序列时,可以将谓词集成到子序列的生成中以提高效率。谓词“不包含 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 故意不详尽。 go 函数中的 ds 是一个差异列表,其中包含所有先前的元素。您可以像这样阅读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]]

【讨论】:

  • 我添加了一条注释,说明 filterCombs 对谓词 p 的期望 - 希望你不要介意。
【解决方案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 应用了一些基本的集合论,并且很容易推广到计数不包含任何两个数字的子集 - 你可以决定它是否合法。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-06-09
    • 2023-01-25
    • 1970-01-01
    • 2020-12-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-04-23
    相关资源
    最近更新 更多