直接生成powerset的特定子集?

时间:2015-04-03 19:04:40

标签: haskell set combinations

Haskell的表现力使我们能够轻松定义一个powerset函数:

import Control.Monad (filterM)

powerset :: [a] -> [[a]]
powerset = filterM (const [True, False])

为了能够执行我的任务,对于所述powerset按特定函数进行排序至关重要,所以我的实现类似于:

import Data.List (sortBy)
import Data.Ord (comparing)

powersetBy :: Ord b => ([a] -> b) -> [a] -> [[a]]
powersetBy f = sortBy (comparing f) . powerset

现在我的问题是,是否有一种方法只能在给定特定startend点的情况下生成powerset的子集,其中f(start) < f(end)|start| < |end|。例如,我的参数是一个整数列表([1,2,3,4,5]),它们按总和排序。现在我想只提取给定范围内的子集,让我们说37。实现这一目标的一种方法是filter powerset仅包括我的范围,但在处理更大的子集时,这似乎(并且)无效:

badFunction :: Ord b => b -> b -> ([a] -> b) -> [a] -> [[a]]
badFunction start end f = filter (\x -> f x >= start && f x <= end) . powersetBy f

badFunction 3 7 sum [1,2,3,4,5]生成[[1,2],[3],[1,3],[4],[1,4],[2,3],[5],[1,2,3],[1,5],[2,4],[1,2,4],[2,5],[3,4]]

现在我的问题是是否有办法直接生成此列表,而不必先生成所有2^n子集,因为它不会检查所有元素而是生成它们,从而大大提高性能&# 34;在飞行中&#34;。

2 个答案:

答案 0 :(得分:9)

如果你想允许完全通用的排序函数,那么就不能来检查powerset的所有元素。 (毕竟,你怎么知道这不是一个特殊的条款,比如说,特定集合[6,8,34,42]与其邻居的排名完全不同?)

但是,您可以通过

使算法速度更快
  1. 仅在过滤后排序:排序为 O n ·log n ),因此您需要保留 n 在这里很低;对于 O n )过滤步骤,它更重要。 (无论如何,元素的数量不会因排序而改变。)
  2. 仅对每个子集应用一次排序功能。
  3. 所以

    import Control.Arrow ((&&&))
    
    lessBadFunction :: Ord b => (b,b) -> ([a]->b) -> [a] -> [[a]]
    lessBadFunction (start,end) f
         = map snd . sortBy (comparing fst)
                   . filter (\(k,_) -> k>=start && k<=end)
                   . map (f &&& id)
                   . powerset
    

    基本上,让我们面对现实,除了非常小的基础之外的任何东西都是不可行的。特定应用“在一定范围内的总和”几乎是packaging problem;有很多有效的方法可以做到这一点,但是你必须放弃对一般子集进行完美通用性和量化的想法。

答案 1 :(得分:2)

由于您的问题本质上是一个约束满足问题,因此使用外部SMT求解器可能是更好的选择;假设您可以负担类型中额外的IO并且需要安装这样的解算器。 SBV库允许构造这样的问题。这是一个编码:

import Data.SBV

-- c is the cost type
-- e is the element type
pick :: (Num e, SymWord e, SymWord c) => c -> c -> ([SBV e] -> SBV c) -> [e] -> IO [[e]]
pick begin end cost xs = do
        solutions <- allSat constraints
        return $ map extract $ extractModels solutions
  where extract ts  = [x | (t, x) <- zip ts xs, t]
        constraints = do tags <- mapM (const free_) xs
                         let tagged    = zip tags xs
                             finalCost = cost [ite t (literal x) 0 | (t, x) <- tagged]    
                         solve [finalCost .>= literal begin, finalCost .<= literal end]

test :: IO [[Integer]]
test = pick 3 7 sum [1,2,3,4,5]

我们得到:

Main> test
[[1,2],[1,3],[1,2,3],[1,4],[1,2,4],[1,5],[2,5],[2,3],[2,4],[3,4],[3],[4],[5]]

对于大型列表,这种技术将击败生成所有子集和过滤;假设成本函数产生合理的约束。 (增加通常是正常的,如果你进行乘法运算,后端解算器会有更难的时间。)

(作为旁注,你不应该使用filterM (const [True, False])来开始生成电源设置!虽然这个表达式很可爱又有趣但效率极低!)