In [1]:
import qualified Data.Map as Map
In [2]:
type DependencyGraph a = Map.Map a [a]
In [3]:
dependencyGraph :: (Ord a) => [a] -> [(a, a)] -> DependencyGraph a
dependencyGraph ps ds =
Map.fromList [ (p, map snd $ filter ((p ==) . fst) ds) | p <- ps]
In [4]:
example = dependencyGraph
["a", "b", "c", "d", "e", "f"]
[("d", "a"), ("b", "f"), ("d", "b"), ("a", "f"), ("c", "d")]
example
In [5]:
import Data.List
import Data.Maybe
In [6]:
{-# LANGUAGE ScopedTypeVariables #-}
In [7]:
buildOrder :: forall a. (Ord a, Show a) => DependencyGraph a -> Either String [a]
buildOrder ds =
let
computeState :: (Either String [a], [a]) -> a -> (Either String [a], [a])
computeState (Left message, _) _ = (Left message, [])
computeState (Right done, visited) key
| key `elem` done = (Right done, visited)
| key `elem` visited = (Left ("Cycle detected: " ++ show key), visited)
| otherwise =
let
tbd = filter (not . (`elem` done)) $ fromJust $ Map.lookup key ds -- !
(done', visited') = foldl' computeState (Right done, key:visited) tbd
in
(fmap (++ [key]) done', visited')
in
fst $ foldl' computeState (Right [], []) $ Map.keys ds
buildOrder example
buildOrder $ Map.fromList [("a",["b"]),("b",["a"])]
In [8]:
import Test.QuickCheck
In [9]:
hasCycles :: forall a. (Ord a) => DependencyGraph a -> Bool
hasCycles ds =
let
hasCyclesFrom :: [a] -> a -> Bool
hasCyclesFrom visited key
| key `elem` visited = True
| otherwise =
maybe False (any (hasCyclesFrom (key:visited))) (Map.lookup key ds)
in
any (hasCyclesFrom []) (Map.keys ds)
In [10]:
checkOrder :: forall a. (Ord a) => DependencyGraph a -> [a] -> Bool
checkOrder ds order =
let
computeState :: Maybe [a] -> a -> Maybe [a]
computeState Nothing _ = Nothing
computeState (Just done) key
| maybe False (all (`elem` done)) (Map.lookup key ds) = Just (key:done)
| otherwise = Nothing
in
length order == length (Map.keys ds) &&
isJust (foldl' computeState (Just []) order)
In [11]:
either (const False) (checkOrder example) $ buildOrder example
checkOrder example $ Map.keys example
In [12]:
hasCycles example
hasCycles $ Map.fromList [("a",["b"]),("b",["a"])]
In [13]:
data GenericDependencyGraph a = GenericDependencyGraph (DependencyGraph a) deriving (Show)
instance (Ord a, Arbitrary a) => Arbitrary (GenericDependencyGraph a) where
arbitrary = do
keys <- fmap nub $ listOf arbitrary
values <- sequence $ replicate (length keys) (sublistOf keys)
return $ GenericDependencyGraph (Map.fromList $ zip keys values)
In [14]:
import Control.Applicative
In [15]:
testGeneric :: GenericDependencyGraph Int -> Bool
testGeneric (GenericDependencyGraph ds) =
let
isCyclicGraph = hasCycles ds
in
either
(const isCyclicGraph)
(not isCyclicGraph &&)
(checkOrder ds <$> buildOrder ds)
In [16]:
quickCheck testGeneric
--verboseCheckWith stdArgs { maxSize = 5 } testGeneric
In [17]:
data AcyclicDependencyGraph a = AcyclicDependencyGraph (DependencyGraph a) deriving (Show)
instance (Ord a, Arbitrary a) => Arbitrary (AcyclicDependencyGraph a) where
arbitrary = do
keys <- fmap nub $ listOf arbitrary
deps <- sequence $ map sublistOf $ map tail $ init . tails $ keys
return $ AcyclicDependencyGraph (Map.fromList $ zip keys deps)
In [18]:
generate arbitrary :: IO (AcyclicDependencyGraph Int)
In [19]:
testAcyclic :: AcyclicDependencyGraph Int -> Bool
testAcyclic (AcyclicDependencyGraph ds) =
either (const False) id $ checkOrder ds <$> buildOrder ds
In [20]:
quickCheckWith stdArgs { maxSize = 5 } testAcyclic