Converting a graph to the width of a Tree bit more complicated than just looking at the width of the graph . If you are looking for a schedule, you only need to return from one branch. When converting a graph to a tree, the result should include results from several branches.
We can use a more general type than Graph a , so that we can search or convert to trees. We can search or transform into trees something with the function a -> [a] . For Graph we will use the function (Map.!) m , where m is a Map . A lookup using a transpose table has a signature of type
breadthFirstSearchUnseen:: Ord r => (a -> r) -> -- how to compare `a`s (a -> Bool) -> -- where to stop (a -> [a]) -> -- where you can go from an `a` [a] -> -- where to start Maybe [a]
Converting a function to a tree containing each reachable node at the earliest depth has a signature like
shortestPathTree :: Ord r => (a -> r) -> -- how to compare `a`s (a -> l) -- what label to put in the tree (a -> [a]) -> -- where you can go from an `a` a -> -- where to start Tree l
We can start a little more broadly with any number of nodes and build a Forest that contains each reachable node in the near future depth.
shortestPathTrees :: Ord r => (a -> r) -> -- how to compare `a`s (a -> l) -- what label to put in the tree (a -> [a]) -> -- where you can go from an `a` [a] -> -- where to start [Tree l]
Search
Performing a conversion to a tree does not help us search, we can perform a search in the first order on the original graph.
import Data.Sequence (viewl, ViewL (..), (><)) import qualified Data.Sequence as Seq import qualified Data.Set as Set breadthFirstSearchUnseen:: Ord r => (a -> r) -> (a -> Bool) -> (a -> [a]) -> [a] -> Maybe [a] breadthFirstSearchUnseen repr p expand = combine Set.empty Seq.empty [] where combine seen queued ancestors unseen = go (seen `Set.union` (Set.fromList . map repr $ unseen)) (queued >< (Seq.fromList . map ((,) ancestors) $ unseen)) go seen queue = case viewl queue of EmptyL -> Nothing (ancestors, a) :< queued -> if pa then Just . reverse $ ancestors' else combine seen queued ancestors' unseen where ancestors' = a:ancestors unseen = filter (flip Set.notMember seen . repr) . expand $ a
The state stored in the aforementioned search algorithm represents the Seq queue of which nodes will be visited next, and the Set nodes that have already been noticed. If instead we keep track of nodes that have already been visited, we could visit the same node several times if we find several paths to the node at the same depth. There a more complete explanation in the answer I wrote this breadth of the first search.
We can easily find the Graph search in terms of our general search.
import qualified Data.Map as Map newtype Graph a = Graph (Map.Map a [a]) deriving (Ord, Eq, Show) bfsGraph :: (Ord a) => Graph a -> (a -> Bool) -> [a] -> Maybe [a] bfsGraph (Graph adj) test = breadthFirstSearchUnseen id test ((Map.!) adj)
We can also write how to independently search for a Tree .
import Data.Tree bfsTrees :: (Ord a) => (a -> Bool) -> [Tree a] -> Maybe [a] bfsTrees test = fmap (map rootLabel) . breadthFirstSearchUnseen rootLabel (test . rootLabel) subForest
Building trees
Building trees wide is a lot harder . Fortunately, Data.Tree already provides ways to build a Tree in first order width from a monodic expansion. The width of the first order will take care of the order, we will only need to track the state for the nodes that we have already seen.
unfoldTreeM_BF is of type Monad m => (b -> m (a, [b])) -> b -> m (Tree a) . m is Monad our calculations will be, b is the data type that we are going to build on the basis of the tree, and a is the type of tree labels. To use it to build a tree, we need to make a function b -> m (a, [b]) . We will rename a to l for the label, and b to a , which we used for our nodes. We need to do a -> m (l, [a]) . For m we will use the State monad from transformers to track the state; the state will be Set nodes whose representation r we have already seen; we will use the State (Set.Set r) monad State (Set.Set r) . In general, we need to provide the function a -> State (Set.Set r) (l, [a]) .
expandUnseen :: Ord r => (a -> r) -> (a -> l) -> (a -> [a]) -> a -> State (Set.Set r) (l, [a]) expandUnseen repr label expand a = do seen <- get let unseen = filter (flip Set.notMember seen . repr) . uniqueBy repr . expand $ a put . Set.union seen . Set.fromList . map repr $ unseen return (label a, unseen)
To build trees, we run a state calculation built by unfoldForestM_BF
shortestPathTrees :: Ord r => (a -> r) -> (a -> l) -> (a -> [a]) -> [a] -> [Tree l] shortestPathTrees repr label expand = run . unfoldForestM_BF k . uniqueBy repr where run = flip evalState Set.empty k = expandUnseen repr label expand
uniqueBy is a nubBy that uses an Ord instance instead of Eq .
uniqueBy :: Ord r => (a -> r) -> [a] -> [a] uniqueBy repr = go Set.empty where go seen [] = [] go seen (x:xs) = if Set.member (repr x) seen then go seen xs else x:go (Set.insert (repr x) seen) xs
We can write the shortest paths to build a tree from Graph in terms of our general construction of the shortest path of a tree
shortestPathsGraph :: Ord a => Graph a -> [a] -> [Tree a] shortestPathsGraph (Graph adj) = shortestPathTrees id id ((Map.!) adj)
We can do the same to filter Forest only on the shortest paths through Forest .
shortestPathsTree :: Ord a => [Tree a] -> [Tree a] shortestPathsTree = shortestPathTrees rootLabel rootLabel subForest