Ради интереса я пытаюсь написать реализацию наивного алгоритма самого длинного пути (для определения длины самого длинного ациклического пути в циклическом графе). Я начал с прямого переноса императивного алгоритма, который работал и работал достаточно хорошо.
data Route = Route {dest:: !Int32, cost:: !Int32}
type Node = [Route]
lPathImperative :: V.Vector Node -> Int32 -> UMV.IOVector Bool -> IO (Int32)
lPathImperative !nodes !nodeID !visited = do
UMV.write visited (fromIntegral nodeID) True
max <- newIORef 0
Prelude.mapM_ (\ Route{dest, cost} -> do
isVisited <- UMV.read visited (fromIntegral dest)
case isVisited of
True -> return ()
False -> do
dist <- fmap (+ cost) $ lPathImperative nodes dest visited
maxVal <- readIORef max
if dist > maxVal then writeIORef max dist else return ())
(nodes V.! (fromIntegral nodeID))
UMV.write visited (fromIntegral nodeID) False
readIORef max
Где visited
- это неупакованный изменяемый вектор логических значений, представляющий, был ли в данный момент посещен каждый узел в графе, все ли инициализированы значением false, а узлы - это вектор узлов.
Затем я попытался сделать его более функциональным, указав max
в качестве значения, которое передается в свертке, а не как IORef, как показано ниже:
lPathFun :: V.Vector Node -> Int32 -> UMV.IOVector Bool -> IO (Int32)
lPathFun !nodes !nodeID !visited = do
UMV.write visited (fromIntegral nodeID) True
let max = CM.foldM acc (0::Int32) (nodes V.! (fromIntegral nodeID))
UMV.write visited (fromIntegral nodeID) False
max
where
acc :: Int32 -> Route -> IO (Int32)
acc maxDist Route{dest,cost} = do
isVisited <- UMV.read visited (fromIntegral dest)
case isVisited of
True -> return maxDist
False -> do
dist <- fmap (+ cost) $ lPathFun nodes dest visited
return $ if dist > maxDist then dist else maxDist
Однако эта версия не может быть завершена, она работает несколько минут (другой - секунды для того же ввода), прежде чем умереть с out of memory (requested 1048576 bytes)
. Я был бы признателен, если бы кто-нибудь мог просмотреть мой код для lPathFun
и увидеть, что я делаю не так. Я пытался сделать все в нем строгим, но это не помогло, а также попытался сделать все ленивым, без изменений. Я даже попытался изменить type node
на V.Vector route
и использовать вместо него строго foldM'
, но безрезультатно.
Я подозреваю, что проблема в утечке места. Это потому, что я попытался перевести lPathFun
в OCaml, и он работал нормально (тот факт, что версия OCaml использует ручную рекурсию, не должен иметь значения: моя функциональная версия Haskell изначально также использовала ручную рекурсию, но имела те же проблемы, что и при использовании foldM) :
type route = {dest: int; cost: int}
type node = route array
let rec lPathFun (nodes: node array) nodeID visited =
visited.(nodeID) <- true;
let rec loop i maxDist =
if i < 0 then maxDist
else
let neighbour = nodes.(nodeID).(i) in
if (not visited.(neighbour.dest))
then
let dist = neighbour.cost + lPathFun nodes neighbour.dest visited in
let newMax = if dist > maxDist then dist else maxDist in
loop (i-1) newMax
else
loop (i-1) maxDist in
let (max: int) = loop (Array.length nodes.(nodeID) - 1) 0 in
visited.(nodeID) <- false;
max;;
Я использую версию GHC 7.8.3.
lPathFun
, должна быть достаточной. - person Tom Ellis   schedule 30.11.2014max
до тех пор, пока после не запишетеFalse
в позициюnodeID
. Разве вы не хотитеmax <- CM.foldM ...; ...; return max
вместоlet max = CM.foldM ...; ...; max
? - person Tom Ellis   schedule 30.11.2014