typerbole-0.0.0.1: src/Data/Graph/Inductive/Helper.hs
module Data.Graph.Inductive.Helper where
import Data.Graph.Inductive as Graph
import qualified Data.Map as Map
import Data.Maybe
import qualified Data.Set as Set
import qualified Data.Tree as Tree
import Safe
findRootPaths :: Graph gr => gr n e -> Graph.Context n e -> (Node, [[Node]])
findRootPaths = findRootPathsBy (\(_, node, _, _) -> node)
findRootPathsBy :: Graph gr => (Graph.Context n e -> a) -> gr n e -> Graph.Context n e -> (a, [[a]])
findRootPathsBy f graph ctx = (f ctx, findRootPathsRec [] ctx) where
findRootPathsRec path ctx'@(inward, _, _, _) = case inward of
[] -> [f ctx' : path]
a -> concatMap (findRootPathsRec (f ctx' : path) . context graph . snd) a
treeRootStatefulBy
:: Graph gr
=> (st -> Graph.Context n e -> (a, st)) -- ^ The stateful fold function
-> st -- ^ The initial state
-> gr n e -- ^ The graph to traverse
-> Graph.Context n e -- ^ The initial context
-> Tree.Tree (a, st) -- ^ The resulting tree of values and states at each node.
treeRootStatefulBy f st graph = trsbRec st where
trsbRec st' ctx'@(inward, _, _, _) =
let val = f st' ctx'
in Tree.Node val (trsbRec (snd val) . context graph . snd <$> inward)
cyclesOfGraph :: Graph gr => gr n l -> [[LNode n]]
cyclesOfGraph graph = fromMaybe [] -- give a default to bring this out of the Maybe
. sequence -- sequence again from [Maybe [...]] to Maybe [[...]]
. fmap -- construct lnodes from each bare node
( sequence -- sequence from [Maybe ...] t0 Maybe [...]
. fmap (\n -> (,) n <$> lab graph n)) -- make the lnode from the node
. filter hasSome -- filter out any with only 1 element
. scc $ graph -- get the strongly connected components
{-|
Given a graph, generate a possibly empty list of subgraphs that are the it's cycles.
-}
cyclicSubgraphs :: forall gr n e. (DynGraph gr, Ord n, Ord e) => gr n e -> [gr n e]
cyclicSubgraphs graph = flip subgraph graph <$> filter hasSomeLI (scc graph) where
-- hasSome but is inclusive of loops (nodes with edges to itself)
hasSomeLI :: [Node] -> Bool
hasSomeLI [] = False
hasSomeLI [n] = not . null $ filter (\(n1,n2,_) -> n == n1 && n1 == n2) (out graph n ++ inn graph n)
hasSomeLI _ = True
-- | Returns true if a list has more than 1 element
hasSome :: [a] -> Bool
hasSome [] = False
hasSome [_] = False
hasSome _ = True
-- | Returns true if a list of contexts has more than one element or a loop in it's only element.
hasSome' :: [Graph.Context n l] -> Bool
hasSome' [] = False
hasSome' [(_, noden, _, outward)] = any ((== noden) . snd) outward
hasSome' _ = True
{-|
Build a graph with no edges from a foldable container of node values.
-}
buildFromNodes :: (DynGraph gr, Foldable t) => t a -> gr a ()
buildFromNodes =
fst . foldr (\n (gr, h : tl) -> (([], h, n, []) & gr, tl)) (Graph.empty, [(0 :: Node)..])
{-|
A version of topsort that returns cycles as groups rather than
incorperating them into it's result arbitrarily.
It's result is a list of eithers, with Left values representing
cycles and Right values as nodes not within a cycle, in an order
similar to what is given by a regular topsort.
-}
topsortWithCycles :: (Graph gr, Ord a) => gr a b -> [Either [a] a]
topsortWithCycles graph =
let
-- Map of all nodes that are within a cycle to the cycle they are a member of.
-- Given the cycle finding function uses fgl's strongly connected components
-- function, there should be no nodes that exist in more than one cycle.
cycleMap = Map.fromList . concat $ (\cy -> zip cy (repeat (Set.fromList cy))) . fmap snd <$> cyclesOfGraph graph
-- Get the initial topsort.
tsorted = topsort' graph
-- Crummy hack to sieve out cycles as they appear in the topsort result
sieveCycles [] = []
sieveCycles (h : tl) = case Map.lookup h cycleMap of
Nothing -> Right h : sieveCycles tl
Just cy -> Left (Set.toList cy) : sieveCycles (filter (`Set.notMember` cy) tl)
in sieveCycles tsorted
{-|
Convert a tree into a list of all of it's paths.
-}
treeToPaths :: Tree.Tree a -> [[a]]
treeToPaths (Tree.Node l []) = [[l]]
treeToPaths (Tree.Node l sbf) = (l :) <$> concat (treeToPaths <$> sbf)
{-|
Build a topsort inclusive of outward edges. Nodes without outward edges
are ignored.
If there are cycles in the graph, Nothing is returned.
-}
edgeyTopsort :: Graph gr => gr n e -> Maybe [(n, e)]
edgeyTopsort graph
| not (null (cyclesOfGraph graph)) = Nothing
| otherwise = Just (unvalidatedEdgeyTopsort graph)
{-|
`edgeyTopsort` but doesn't check for cycles first.
-}
unvalidatedEdgeyTopsort :: Graph gr => gr n e -> [(n, e)]
unvalidatedEdgeyTopsort graph =
{-
In a cycle-less graph, there should always be 1 node
that has no inward edge. If there's not, then we've
finished the topsort
-}
let candidate = headMay $ gsel (null . inn') graph
in case flip match graph . node' <$> candidate of
Nothing -> []
Just (Nothing, _) -> []
Just (Just ctx, graph') -> fmap (\(_, _, l) -> (lab' ctx, l)) (out' ctx) ++ unvalidatedEdgeyTopsort graph'