55-- | This module provides a dependency graph implementation.
66module Constrained.Graph (
77 Graph ,
8+ edges ,
9+ opEdges ,
810 opGraph ,
11+ mkGraph ,
912 nodes ,
1013 deleteNode ,
1114 subtractGraph ,
@@ -22,20 +25,21 @@ module Constrained.Graph (
2225
2326import Control.Monad
2427import Data.Foldable
25- import Data.List (sortOn )
28+ import Data.List (sortOn , nub )
2629import Data.Map (Map )
2730import Data.Map qualified as Map
2831import Data.Maybe
2932import Data.Set (Set )
3033import Data.Set qualified as Set
3134import Prettyprinter
35+ import Test.QuickCheck
3236
3337-- | A graph with unlabeled edges for keeping track of dependencies
3438data Graph node = Graph
3539 { edges :: ! (Map node (Set node ))
3640 , opEdges :: ! (Map node (Set node ))
3741 }
38- deriving (Show )
42+ deriving (Show , Eq )
3943
4044instance Ord node => Semigroup (Graph node ) where
4145 Graph e o <> Graph e' o' =
@@ -55,6 +59,37 @@ instance Pretty n => Pretty (Graph n) where
5559 | (n, ns) <- Map. toList (edges gr)
5660 ]
5761
62+ -- | Construct a graph
63+ mkGraph :: Ord node => Map node (Set node ) -> Graph node
64+ mkGraph e0 = Graph e $ Map. unionsWith (<>)
65+ [ Map. fromList $ (p, mempty ) : [ (c, Set. singleton p)
66+ | c <- Set. toList cs
67+ ]
68+ | (p, cs) <- Map. toList e
69+ ]
70+ where e = Map. unionWith (<>) e0 (Map. fromList [ (c, mempty ) | (_, cs) <- Map. toList e0
71+ , c <- Set. toList cs
72+ ])
73+
74+ instance (Arbitrary node , Ord node ) => Arbitrary (Graph node ) where
75+ arbitrary =
76+ frequency [ (1 , mkGraph <$> arbitrary)
77+ , (3 , do order <- nub <$> arbitrary
78+ mkGraph <$> buildGraph order
79+ )
80+ ]
81+ where buildGraph [] = return mempty
82+ buildGraph [n] = return (Map. singleton n mempty )
83+ buildGraph (n: ns) = do
84+ deps <- listOf (elements ns)
85+ Map. insert n (Set. fromList deps) <$> buildGraph ns
86+ shrink g =
87+ [ mkGraph e'
88+ | e <- shrink (edges g)
89+ -- If we don't do this it's very easy to introduce a shrink-loop
90+ , let e' = fmap (\ xs -> Set. filter (`Map.member` e) xs) e
91+ ]
92+
5893-- | Get all the nodes of a graph
5994nodes :: Graph node -> Set node
6095nodes (Graph e _) = Map. keysSet e
@@ -102,11 +137,12 @@ irreflexiveDependencyOn xs ys =
102137
103138-- | Get all down-stream dependencies of a node
104139transitiveDependencies :: Ord node => node -> Graph node -> Set node
105- transitiveDependencies x (Graph e _) = go (Set. singleton x) x
140+ transitiveDependencies x (Graph e _) = go mempty (Set. toList $ fromMaybe mempty $ Map. lookup x e)
106141 where
107- go seen y = ys <> foldMap (go $ Set. insert y seen) (Set. difference ys seen)
108- where
109- ys = fromMaybe mempty (Map. lookup y e)
142+ go deps [] = deps
143+ go deps (y: ys)
144+ | y `Set.member` deps = go deps ys
145+ | otherwise = go (Set. insert y deps) (ys ++ Set. toList (fromMaybe mempty $ Map. lookup y e))
110146
111147-- | Take the transitive closure of the graph
112148transitiveClosure :: Ord node => Graph node -> Graph node
@@ -133,10 +169,11 @@ topsort gr@(Graph e _) = go [] e
133169 else Left . concat . take 1 . sortOn length . filter (not . null ) . map (findCycle gr) $ Map. keys e
134170
135171-- | Simple DFS cycle finding
136- -- TODO: tests for this, currently it can produce a stem with a cycle after it
137172findCycle :: Ord node => Graph node -> node -> [node ]
138- findCycle (Graph e _) node = concat . take 1 $ go mempty node
173+ findCycle g @ (Graph e _) node = concat . take 1 $ filter loopy $ go mempty node
139174 where
175+ loopy [] = False
176+ loopy c@ (x: _) = dependsOn (last c) x g
140177 go seen n
141178 | n `Set.member` seen = [[] ]
142179 | otherwise = do
0 commit comments