我正在尝试在Haskell中开发一个启发式搜索库,并且使算法以某种方式表现的主要因素之一是数据结构;所以我宣布了这个课程:
class DataStructure ds where
add :: Eq a => Node a -> ds a -> ds a
addList :: Eq a => [Node a] -> ds a -> ds a
next :: Eq a => ds a -> (ds a, Node a)
isEmpty :: Eq a => ds a -> Bool
然后,这个类的实例用于扩展节点的通用搜索算法:
generalSearch :: (DataStructure ds, Eq a, Hashable a)
=> ProblemSpace a -- ^ 'ProblemSpace' to be solved
-> Cost a -- ^ 'Cost' function to use
-> Heuristic a -- ^ 'Heuristic' function to use
-> ds a -- ^ 'DataStructure' that manages the node expansion
-> [Node a] -- ^ Returns the infinite list of final nodes (solutions)
generalSearch problem g h open
| isEmpty open = []
| getGoalF problem (getState n) = n : generalSearch problem g h open'
| otherwise = generalSearch problem g h open''
where (open', n) = next open
expanded = expand n g h (getActions problem)
open'' = addList expanded open'
如果没有模块的完整上下文,最后一段代码很难理解,但重要的部分是最后一行:它使用DataStructure
方法addList
将刚刚合并到一起扩展节点与执行所携带的其余打开列表。
有些算法要求打开列表以某种方式排序:升序成本,升序启发式...我使用heap
包来构建PriorityQueue
实现,如下所示: / p>
data PriorityQueue a =
PriorityQueue { priorityQueueToHeap :: MinPrioHeap Double (Node a)
, valueFunction :: Node a -> Double }
因此,此队列按照valueFunction
按升序存储节点。此问题的相关方法addList
的实现方式如下:
addList :: Eq a => [Node a] -> PriorityQueue a -> PriorityQueue a
addList xs (PriorityQueue h f) = PriorityQueue (union h xs') f
where xs' = fromList $ map (\n -> (f n, n)) xs
由于我对Haskell的经验不足,这似乎是一个很好的方法,但事实证明,在测试它时,执行速度非常慢。在进行了一些分析之后,我发现由于垃圾收集而导致问题停顿了很多,成本中心发现union
中的addList
方法是时间上最昂贵的({I}}可以理解,由于排序),但也在空间(这是最远的分配)。
COST CENTRE MODULE SRC %time %alloc
union Data.Heap.Internal Data/Heap/Internal.hs:(134,1)-(141,79) 42.8 32.7
visited Search.General src/Search/General.hs:77:43-53 14.6 0.0
expand Search.General src/Search/General.hs:(70,40)-(76,78) 13.6 14.0
cost Search.General src/Search/General.hs:74:70-81 7.8 7.9
generalSearch Search.General src/Search/General.hs:(52,1)-(58,37) 6.7 13.6
fromList Data.Heap Data/Heap.hs:137:1-34 4.7 11.7
size Data.Heap.Internal Data/Heap/Internal.hs:(98,1)-(99,23) 3.4 8.3
rank Data.Heap.Internal Data/Heap/Internal.hs:(93,1)-(94,23) 2.7 8.0
expand Search.General src/Search/General.hs:(69,1)-(77,53) 1.4 3.4
时间问题似乎只在开放节点列表很大时才会出现,所以我猜它与堆的不可变性有关,在每次新扩展中都会被写入和删除。有没有办法防止这种行为,并减少垃圾收集?我应该使用不同类型的数据结构来构建优先级队列吗?