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
现在我的问题是,是否有一种方法只能在给定特定start
和end
点的情况下生成powerset的子集,其中f(start) < f(end)
和|start| < |end|
。例如,我的参数是一个整数列表([1,2,3,4,5]
),它们按总和排序。现在我想只提取给定范围内的子集,让我们说3
到7
。实现这一目标的一种方法是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;。
答案 0 :(得分:9)
如果你想允许完全通用的排序函数,那么就不能来检查powerset的所有元素。 (毕竟,你怎么知道这不是一个特殊的条款,比如说,特定集合[6,8,34,42]
与其邻居的排名完全不同?)
但是,您可以通过
使算法速度更快所以
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])
来开始生成电源设置!虽然这个表达式很可爱又有趣但效率极低!)