cauldron-0.8.0.0: lib-graph/Cauldron/Graph/Algorithm.hs
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE ViewPatterns #-}
module Cauldron.Graph.Algorithm
( reverseTopSort,
)
where
import Cauldron.Graph
import Data.Foldable (for_)
import Data.Foldable qualified
import Data.Function ((&))
import Data.Graph qualified
import Data.List.NonEmpty
import Data.Map.Strict qualified
import Data.Sequence qualified
import Data.Set qualified
reverseTopSort :: (Ord a) => AdjacencyMap a -> Either (NonEmpty a) [a]
reverseTopSort g = do
let theEdges = do
(i, o) <- adjacencyList g
[(i, i, o)]
sccs = Data.Graph.stronglyConnComp theEdges
for_ sccs $ \case
Data.Graph.AcyclicSCC _ -> pure ()
Data.Graph.NECyclicSCC vs -> do
let aCycle = findCycleInSCC g vs
Left aCycle
let (g', nodeFromVertex, _) = Data.Graph.graphFromEdges theEdges
Right $ do
ves <- Data.Graph.reverseTopSort g'
let (v, _, _) = nodeFromVertex ves
[v]
findCycleInSCC :: (Ord a) => AdjacencyMap a -> NonEmpty a -> NonEmpty a
findCycleInSCC g scc@(start :| _) = go start (Data.Set.singleton start) (Data.Sequence.singleton start)
where
sccSet = Data.Set.fromList . Data.Foldable.toList $ scc
isInScc = (`Data.Set.member` sccSet)
am = adjacencyMap $ Cauldron.Graph.induce isInScc g
firstChildOf v =
case Data.Set.toList <$> Data.Map.Strict.lookup v am of
Nothing -> error "findCycleInSCC: node not in adjacency map"
-- In a SCC, all vertices should have at least one outgoing edge!
Just [] -> error "findCycleInSCC: SCC node with no outgoing edge"
Just (child : _) -> child
go current visited cycleAcc =
let child = firstChildOf current
in if child `Data.Set.member` visited
then
Data.List.NonEmpty.fromList $ Data.Foldable.toList $ Data.Sequence.dropWhileL (/= child) cycleAcc
else
go child (visited & Data.Set.insert child) (cycleAcc Data.Sequence.|> child)