module Clash.Util.Graph (topSort, reverseTopSort) where
import Data.Tuple (swap)
import Data.Foldable (foldlM)
import qualified Data.IntMap.Strict as IntMap
import qualified Data.IntSet as IntSet
data Marker
= Temporary
| Permanent
headSafe :: [a] -> Maybe a
headSafe [] = Nothing
headSafe (a:_) = Just a
topSortVisit'
:: IntMap.IntMap [Int]
-> IntSet.IntSet
-> IntMap.IntMap Marker
-> [Int]
-> Int
-> Either String (IntSet.IntSet, IntMap.IntMap Marker, [Int])
topSortVisit' edges unmarked marked sorted node =
case IntMap.lookup node marked of
Just Permanent -> Right (unmarked, marked, sorted)
Just Temporary -> Left "cycle detected: cannot topsort cyclic graph"
Nothing -> do
let marked' = IntMap.insert node Temporary marked
let unmarked' = IntSet.delete node unmarked
let nodeToM = IntMap.findWithDefault [] node edges
(unmarked'', marked'', sorted'') <-
foldlM visit (unmarked', marked', sorted) nodeToM
let marked''' = IntMap.insert node Permanent marked''
return (unmarked'', marked''', node : sorted'')
where
visit (unmarked', marked', sorted') node' =
topSortVisit' edges unmarked' marked' sorted' node'
topSortVisit
:: IntMap.IntMap [Int]
-> IntSet.IntSet
-> IntMap.IntMap Marker
-> [Int]
-> Int
-> Either String (IntSet.IntSet, IntMap.IntMap Marker, [Int])
topSortVisit edges unmarked marked sorted node = do
(unmarked', marked', sorted') <-
topSortVisit' edges unmarked marked sorted node
case headSafe (IntSet.toList unmarked') of
Nothing -> return (unmarked', marked', sorted')
Just node' -> topSortVisit edges unmarked' marked' sorted' node'
topSort
:: [(Int, a)]
-> [(Int, Int)]
-> Either String [a]
topSort [] [] = Right []
topSort [] _edges = Left "Node list was empty, but edges non-empty"
topSort nodes@(node:_) edges = do
_ <- mapM (\(n, m) -> checkNode n >> checkNode m) edges
(_, _, sorted) <-
topSortVisit edges' (IntMap.keysSet nodes') IntMap.empty [] (fst node)
mapM lookup' sorted
where
nodes' = IntMap.fromList nodes
edges' = foldl insert IntMap.empty edges
insert im (n, m) = IntMap.alter (insert' m) n im
insert' m Nothing = Just [m]
insert' m (Just ms) = Just (m:ms)
lookup' n =
case IntMap.lookup n nodes' of
Nothing
-> Left ("Node " ++ show n ++ " in edge list, but not in node list.")
Just n'
-> Right n'
checkNode n
| IntMap.notMember n nodes' =
Left ("Node " ++ show n ++ " in edge list, but not in node list.")
| otherwise =
Right n
reverseTopSort
:: [(Int, a)]
-> [(Int, Int)]
-> Either String [a]
reverseTopSort nodes edges =
topSort nodes (map swap edges)