module Database.Schema.Migrations.CycleDetection
( hasCycle
)
where
import Data.Graph.Inductive.Graph
( Graph(..)
, Node
, nodes
, edges
)
import Control.Monad.State ( State, evalState, gets, get, put )
import Control.Monad ( forM )
import Data.Maybe ( fromJust )
import Data.List ( findIndex )
data Mark = White | Gray | Black
type CycleDetectionState = [(Node, Mark)]
hasCycle :: Graph g => g a b -> Bool
hasCycle g = evalState (hasCycle' g) [(n, White) | n <- nodes g]
getMark :: Int -> State CycleDetectionState Mark
getMark n = gets (fromJust . lookup n)
replace :: [a] -> Int -> a -> [a]
replace elems index val
| index > length elems = error "replacement index too large"
| otherwise = (take index elems) ++
[val] ++
(reverse $ take ((length elems) (index + 1)) $ reverse elems)
setMark :: Int -> Mark -> State CycleDetectionState ()
setMark n mark = do
st <- get
let index = fromJust $ findIndex (\(n', _) -> n' == n) st
put $ replace st index (n, mark)
hasCycle' :: Graph g => g a b -> State CycleDetectionState Bool
hasCycle' g = do
result <- forM (nodes g) $ \n -> do
m <- getMark n
case m of
White -> visit g n
_ -> return False
return $ or result
visit :: Graph g => g a b -> Node -> State CycleDetectionState Bool
visit g n = do
setMark n Gray
result <- forM [ v | (u,v) <- edges g, u == n ] $ \node -> do
m <- getMark node
case m of
Gray -> return True
White -> visit g node
_ -> return False
case or result of
True -> return True
False -> do
setMark n Black
return False