我试图找到在降序排序的搜索空间中第一次出现,它满足某个谓词。
选择这种策略是因为计算谓词可能非常昂贵,并且在前者中找到解决方案的概率非常高。
这是解决方案,首先构建所有可能解决方案的列表,然后安排并生成线性搜索。
import Data.Ord
import Data.List
search :: (Ord a, Num a) => ([a] -> Bool) -> [[a]] -> Maybe [a]
search p = find p . sortOn (Down . sum) . sequence
main = print $ search ((<25) . sum) [[10,2], [10,8,6], [8]]
Just [10,6,8]
有没有办法按降序生成此空间的元素而不进行排序?
答案 0 :(得分:4)
在这个确切的情况下,空间中有一个明确的最佳元素,如果任何元素与谓词匹配,那么最好的元素就是:
-- I have, over the years, found many uses for ensure
ensure p x = x <$ guard p x
search p = ensure p . map minimum
(<25) . sum
是一个占位符,但Down . sum
确切地说是如果您的谓词只是一个示例,但您的启发式实际上是求和,则可以使用优先级队列来搜索空间。为简单起见,我将使用[(b,a)]
作为优先级b
和值a
的优先级队列,保持列表按b
排序的不变量。当然,如果你想要效率,你应该使用更好的实现。
现在我们基本上只是重新实现sequence
以按优先级顺序生成其元素,并将其生成的列表的总和保持为优先级。引入优先级队列不变量是一个小的一次性成本。
import Data.List
import Data.Ord
increasingSums :: (Ord a, Num a) => [[a]] -> [[a]]
increasingSums = map snd . go . map sort where
go [] = [(0,[])]
go (xs:xss) = let recurse = go xss in mergeOn fst
[ [ (total+h, h:ts)
| (total, ts) <- recurse
]
| h <- xs
]
唯一缺少的是mergeOn
,它将一组优先级队列展平为一个:
mergeOn :: Ord b => (a -> b) -> [[a]] -> [a]
mergeOn f = go . sortOn (f . head) . filter (not . null) where
go [] = []
go ([x]:xss) = x : go xss
go ((x:xs):xss) = x : go (insertBy (comparing (f . head)) xs xss)
在ghci
中进行测试,我们可以看到此表达式在非愚蠢的时间内完成:
> take 10 . increasingSums . replicate 4 $ [1..1000]
[[1,1,1,1],[2,1,1,1],[1,2,1,1],[1,1,2,1],[1,1,1,2],[2,1,1,2],[1,2,1,2],[1,1,2,2],[1,1,1,3],[2,1,2,1]]
而这个表达式不是:
> take 10 . sortOn sum . sequence . replicate 4 $ [1..1000]
^C^C^C^COMG how do I quit
同时,按排序顺序生成完整的总和列表也很有竞争力(至少在编译之前,我没有测试优化版本是否也大致相同):
> :set +s
> sum . map sum . increasingSums . replicate 4 $ [1..30]
50220000
(1.99 secs, 1,066,135,432 bytes)
> sum . map sum . sortOn sum . sequence . replicate 4 $ [1..30]
50220000
(2.60 secs, 2,226,497,344 bytes)
Down . sum
是占位符最后,如果你的启发式只是一个例子,并且你想要一个适用于所有启发式的完全通用的解决方案,那你就不走运了。在搜索空间中进行结构化漫游需要了解有关该结构的特殊内容。 (例如,上面我们知道如果x<y
然后total+x<total+y
,我们利用它来便宜地维护我们的优先级队列。)