Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BFS in *.Algorithm modules #217

Merged
merged 24 commits into from
Jul 20, 2019
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
9437186
bfs first draft
jitwit Jun 20, 2019
4597db8
rewrote bfsForestFrom as single monadic fold
jitwit Jun 20, 2019
1c001a3
added tests for bfs
jitwit Jun 20, 2019
9f6ba55
documentation for bfs procedures
jitwit Jun 20, 2019
bf47a34
Perform bfs with KL graphs
jitwit Jun 21, 2019
dbcde55
small change to bfs helpers
jitwit Jun 21, 2019
84a2859
bfs flattening was incorrect
jitwit Jun 21, 2019
cdc0487
include changes to dfs/bfs
jitwit Jun 22, 2019
c33cb5d
still use KL for ord, but set-based dfs for AIMs according to bench
jitwit Jun 22, 2019
f40bfe1
cleaner dfs/bfs forest expression
jitwit Jun 22, 2019
f0012e8
Add complexity analysis for bfs in documentation
jitwit Jun 22, 2019
11b013d
less confusing variable name
jitwit Jun 22, 2019
785e84a
avoid checking if vertices are in graph for bfsForest/dfsForest
jitwit Jun 24, 2019
33046f6
actually use procedure that avoids checking...
jitwit Jun 24, 2019
8d779f1
Merge remote-tracking branch 'upstream/master'
jitwit Jun 27, 2019
6f572c0
revert to kgl dfs for adjacencyintmap and fix a small haddocks issue
jitwit Jul 11, 2019
7691fe4
deleted stuarray implementation as it benched slower
jitwit Jul 12, 2019
40e045c
documentation fixes and cleaning leftovers
jitwit Jul 13, 2019
a04d342
module re-ordering, include test cases for dfs, switch reachable to bfs
jitwit Jul 13, 2019
f43d583
Document changes to reachable & match test oder with module order
jitwit Jul 14, 2019
b83063f
another re-ordering...
jitwit Jul 15, 2019
dbbb899
added test cases as examples in documentation
jitwit Jul 17, 2019
27d3350
small rephrasing
jitwit Jul 17, 2019
5ca42b1
another bfs/dfs out of order!
jitwit Jul 17, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 60 additions & 3 deletions src/Algebra/Graph/AdjacencyIntMap/Algorithm.hs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
{-# language LambdaCase #-}

-----------------------------------------------------------------------------
-- |
-- Module : Algebra.Graph.AdjacencyIntMap.Algorithm
Expand All @@ -15,13 +17,15 @@
-----------------------------------------------------------------------------
module Algebra.Graph.AdjacencyIntMap.Algorithm (
-- * Algorithms
dfsForest, dfsForestFrom, dfs, reachable, topSort, isAcyclic,

bfsForest, bfsForestFrom, bfs, dfsForest, dfsForestFrom, dfs, reachable,
topSort, isAcyclic,

-- * Correctness properties
isDfsForestOf, isTopSortOf
) where

import Control.Monad
import Control.Monad.State.Strict
import Data.Maybe
import Data.Tree

Expand All @@ -31,6 +35,56 @@ import qualified Data.Graph.Typed as Typed
import qualified Data.IntMap.Strict as IntMap
import qualified Data.IntSet as IntSet

-- | Compute the forest of a graph's vertices in breadth first order.
-- Complexity: /O(n+m*log n)/ time and /O(n+m)/ space.
--
-- @
-- bfsForest 'empty' == []
-- 'forest' (bfsForest $ 'edge' 1 1) == 'vertex' 1
-- 'forest' (bfsForest $ 'edge' 1 2) == 'edge' 1 2
-- 'forest' (bfsForest $ 'edge' 2 1) == 'vertices' [1,2]
-- 'isSubgraphOf' ('forest' $ bfsForest x) x == True
-- bfsForest . 'forest' . bfsForest == bfsForest
-- 'forest' (bfsForest ('circuit' [1..5] + 'circuit' [5,4..1])) == 'path' [1,2,3] + 'path' [1,5,4]
-- @
bfsForest :: AdjacencyIntMap -> Forest Int
bfsForest g = bfsForestFrom' (vertexList g) g

-- | Like 'bfsForest', but the traversal is seeded by a list of
-- vertices. Let /s/ be the number of seed vertices. Complexity:
-- /O(n+(s+m)*log n)/ time and /O(n+m)/ space.
--
-- @
-- 'forest' (bfsForestFrom [1,2] $ 'edge' 1 2) == 'vertices' [1,2]
-- 'forest' (bfsForestFrom [2] $ 'edge' 2 1) == 'vertex' 2
-- 'forest' (bfsForestFrom [3] $ 'edge' 1 2) == empty
-- 'forest' (bfsForestFrom [3] ('circuit' [1..5] + 'circuit' [5,4..1])) == 'path' [3,2,1] + 'path' [3,4,5]
-- @
bfsForestFrom :: [Int] -> AdjacencyIntMap -> Forest Int
bfsForestFrom vs g = bfsForestFrom' [ v | v <- vs, hasVertex v g] g

bfsForestFrom' :: [Int] -> AdjacencyIntMap -> Forest Int
bfsForestFrom' vs g = evalState (bff vs) IntSet.empty where
bff [] = return []
bff (v:vs) = discovered v >>= \case
False -> bff vs
True -> (:) <$> unfoldTreeM_BF walk v <*> bff vs
walk v = (v,) <$> adjacentM v
adjacentM v = filterM discovered $ IntSet.toList (postIntSet v g)
discovered v = do new <- gets (not . IntSet.member v)
when new $ modify' (IntSet.insert v)
return new

-- | Like 'bfsForestFrom' with the resulting forest flattened to a
-- list of vertices. Let /s/ be the number of seed
-- vertices. Complexity: /O(n+(s+m)*log n)/ time and /O(n+m)/ space.
--
-- @
-- bfs [3] ('circuit' [1..5] + 'circuit' [5,4..1]) == [[3],[2,4],[1,5]]
-- @
bfs :: [Int] -> AdjacencyIntMap -> [[Int]]
bfs vs = bfsForestFrom vs >=> levels

-- | Compute the /depth-first search/ forest of a graph that corresponds to
-- searching from each of the graph vertices in the 'Ord' @a@ order.
--
Expand All @@ -50,6 +104,7 @@ import qualified Data.IntSet as IntSet
-- , Node { rootLabel = 3
-- , subForest = [ Node { rootLabel = 4
-- , subForest = [] }]}]
-- 'forest' (dfsForest $ 'circuit' [1..5] + 'circuit' [5,4..1]) == 'path' [1,2,3,4,5]
-- @
dfsForest :: AdjacencyIntMap -> Forest Int
dfsForest = Typed.dfsForest . Typed.fromAdjacencyIntMap
Expand All @@ -75,6 +130,7 @@ dfsForest = Typed.dfsForest . Typed.fromAdjacencyIntMap
-- , subForest = [] }
-- , Node { rootLabel = 4
-- , subForest = [] }]
-- 'forest' (dfsForestFrom [3] $ 'circuit' [1..5] + 'circuit' [5,4..1]) == 'path' [3,2,1,5,4]
-- @
dfsForestFrom :: [Int] -> AdjacencyIntMap -> Forest Int
dfsForestFrom vs = Typed.dfsForestFrom vs . Typed.fromAdjacencyIntMap
Expand All @@ -93,6 +149,7 @@ dfsForestFrom vs = Typed.dfsForestFrom vs . Typed.fromAdjacencyIntMap
-- dfs [] $ x == []
-- dfs [1,4] $ 3 * (1 + 4) * (1 + 5) == [1,5,4]
-- 'isSubgraphOf' ('vertices' $ dfs vs x) x == True
-- dfs [3] $ 'circuit' [1..5] + 'circuit' [5,4..1] == [3,2,1,5,4]
-- @
dfs :: [Int] -> AdjacencyIntMap -> [Int]
dfs vs = concatMap flatten . dfsForestFrom vs
Expand All @@ -113,7 +170,7 @@ dfs vs = concatMap flatten . dfsForestFrom vs
-- 'isSubgraphOf' ('vertices' $ reachable x y) y == True
-- @
reachable :: Int -> AdjacencyIntMap -> [Int]
reachable x = dfs [x]
reachable x = concat . bfs [x]
jitwit marked this conversation as resolved.
Show resolved Hide resolved

-- | Compute the /topological sort/ of a graph or return @Nothing@ if the graph
-- is cyclic.
Expand Down
70 changes: 64 additions & 6 deletions src/Algebra/Graph/AdjacencyMap/Algorithm.hs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
{-# language LambdaCase #-}

-----------------------------------------------------------------------------
-- |
-- Module : Algebra.Graph.AdjacencyMap.Algorithm
Expand All @@ -15,13 +17,15 @@
-----------------------------------------------------------------------------
module Algebra.Graph.AdjacencyMap.Algorithm (
-- * Algorithms
dfsForest, dfsForestFrom, dfs, reachable, topSort, isAcyclic, scc,

bfsForest, bfsForestFrom, bfs, dfsForest, dfsForestFrom, dfs, reachable,
topSort, isAcyclic, scc,

-- * Correctness properties
isDfsForestOf, isTopSortOf
) where

import Control.Monad
import Control.Monad.State.Strict
import Data.Foldable (toList)
import Data.Maybe
import Data.Tree
Expand All @@ -34,6 +38,57 @@ import qualified Data.Graph.Typed as Typed
import qualified Data.Map.Strict as Map
import qualified Data.Set as Set

-- | Compute the forest of a graph's vertices in breadth first order. Complexity:
-- /O(n+m*log n)/ time and /O(n+m)/ space.
--
-- @
-- bfsForest 'empty' == []
-- 'forest' (bfsForest $ 'edge' 1 1) == 'vertex' 1
-- 'forest' (bfsForest $ 'edge' 1 2) == 'edge' 1 2
-- 'forest' (bfsForest $ 'edge' 2 1) == 'vertices' [1,2]
-- 'isSubgraphOf' ('forest' $ bfsForest x) x == True
-- bfsForest . 'forest' . bfsForest == bfsForest
-- 'forest' (bfsForest ('circuit' [1..5] + 'circuit' [5,4..1])) == 'path' [1,2,3] + 'path' [1,5,4]
-- @
bfsForest :: Ord a => AdjacencyMap a -> Forest a
bfsForest g = bfsForestFrom' (vertexList g) g

-- | Like 'bfsForest', but the traversal is seeded by a list of
-- vertices. Seed vertices not in the graph are ignored. Let /s/ be
-- the number of seed vertices. Complexity: /O(n+(s+m)*log n)/ time
-- and /O(n+m)/ space.
--
-- @
-- 'forest' (bfsForestFrom [1,2] $ 'edge' 1 2) == 'vertices' [1,2]
-- 'forest' (bfsForestFrom [2] $ 'edge' 1 2) == 'vertex' 2
-- 'forest' (bfsForestFrom [3] $ 'edge' 1 2) == empty
-- 'forest' (bfsForestFrom [3] ('circuit' [1..5] + 'circuit' [5,4..1])) == 'path' [3,2,1] + 'path' [3,4,5]
-- @
bfsForestFrom :: Ord a => [a] -> AdjacencyMap a -> Forest a
bfsForestFrom vs g = bfsForestFrom' [ v | v <- vs, hasVertex v g ] g

bfsForestFrom' :: Ord a => [a] -> AdjacencyMap a -> Forest a
bfsForestFrom' vs g = evalState (bff vs) Set.empty where
bff [] = return []
bff (v:vs) = discovered v >>= \case
False -> bff vs
True -> (:) <$> unfoldTreeM_BF walk v <*> bff vs
walk v = (v,) <$> adjacentM v
adjacentM v = filterM discovered $ Set.toList (postSet v g)
discovered v = do new <- gets (not . Set.member v)
when new $ modify' (Set.insert v)
return new

-- | Like 'bfsForestFrom' with the resulting forest flattened to a
-- list of vertices. Let /s/ be the number of seed
-- vertices. Complexity: /O(n+(s+m)*log n)/ time and /O(n+m)/ space.
--
-- @
-- bfs [3] ('circuit' [1..5] + ('circuit' [5,4..1])) == [[3],[2,4],[1,5]]
-- @
bfs :: Ord a => [a] -> AdjacencyMap a -> [[a]]
bfs vs = bfsForestFrom vs >=> levels

-- | Compute the /depth-first search/ forest of a graph that corresponds to
-- searching from each of the graph vertices in the 'Ord' @a@ order.
--
Expand All @@ -53,6 +108,7 @@ import qualified Data.Set as Set
-- , Node { rootLabel = 3
-- , subForest = [ Node { rootLabel = 4
-- , subForest = [] }]}]
-- 'forest' (dfsForest $ 'circuit' [1..5] + 'circuit' [5,4..1]) == 'path' [1,2,3,4,5]
-- @
dfsForest :: Ord a => AdjacencyMap a -> Forest a
dfsForest g = dfsForestFrom (vertexList g) g
Expand All @@ -78,6 +134,7 @@ dfsForest g = dfsForestFrom (vertexList g) g
-- , subForest = [] }
-- , Node { rootLabel = 4
-- , subForest = [] }]
-- 'forest' (dfsForestFrom [3] $ 'circuit' [1..5] + 'circuit' [5,4..1]) == 'path' [3,2,1,5,4]
-- @
dfsForestFrom :: Ord a => [a] -> AdjacencyMap a -> Forest a
dfsForestFrom vs = Typed.dfsForestFrom vs . Typed.fromAdjacencyMap
Expand All @@ -96,13 +153,14 @@ dfsForestFrom vs = Typed.dfsForestFrom vs . Typed.fromAdjacencyMap
-- dfs [] $ x == []
-- dfs [1,4] $ 3 * (1 + 4) * (1 + 5) == [1,5,4]
-- 'isSubgraphOf' ('vertices' $ dfs vs x) x == True
-- dfs [3] $ 'circuit' [1..5] + 'circuit' [5,4..1] == [3,2,1,5,4]
-- @
dfs :: Ord a => [a] -> AdjacencyMap a -> [a]
dfs vs = concatMap flatten . dfsForestFrom vs

-- | Compute the list of vertices that are /reachable/ from a given source
-- vertex in a graph. The vertices in the resulting list appear in the
-- /depth-first order/.
-- | Compute the list of vertices that are /reachable/ from a given
-- source vertex in a graph. The vertices in the resulting list appear
-- in the /depth-first order/.
--
-- @
-- reachable x $ 'empty' == []
Expand All @@ -116,7 +174,7 @@ dfs vs = concatMap flatten . dfsForestFrom vs
-- 'isSubgraphOf' ('vertices' $ reachable x y) y == True
-- @
reachable :: Ord a => a -> AdjacencyMap a -> [a]
reachable x = dfs [x]
reachable x = concat . bfs [x]
jitwit marked this conversation as resolved.
Show resolved Hide resolved

-- | Compute the /topological sort/ of a graph or return @Nothing@ if the graph
-- is cyclic.
Expand Down
1 change: 1 addition & 0 deletions src/Data/Graph/Typed.hs
Original file line number Diff line number Diff line change
Expand Up @@ -181,3 +181,4 @@ dfs vs = concatMap flatten . dfsForestFrom vs
-- @
topSort :: GraphKL a -> [a]
topSort (GraphKL g r _) = map r (KL.topSort g)

jitwit marked this conversation as resolved.
Show resolved Hide resolved
9 changes: 9 additions & 0 deletions test/Algebra/Graph/Test/API.hs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ data API g c where
, dfsForest :: forall a. c a => g a -> Forest a
, dfsForestFrom :: forall a. c a => [a] -> g a -> Forest a
, dfs :: forall a. c a => [a] -> g a -> [a]
, bfsForest :: forall a. c a => g a -> Forest a
jitwit marked this conversation as resolved.
Show resolved Hide resolved
, bfsForestFrom :: forall a. c a => [a] -> g a -> Forest a
, bfs :: forall a. c a => [a] -> g a -> [[a]]
, reachable :: forall a. c a => a -> g a -> [a]
, topSort :: forall a. c a => g a -> Maybe [a]
, isAcyclic :: forall a. c a => g a -> Bool
Expand Down Expand Up @@ -179,6 +182,9 @@ adjacencyMapAPI = API
, dfsForest = AM.dfsForest
, dfsForestFrom = AM.dfsForestFrom
, dfs = AM.dfs
, bfsForest = AM.bfsForest
, bfsForestFrom = AM.bfsForestFrom
, bfs = AM.bfs
, reachable = AM.reachable
, topSort = AM.topSort
, isAcyclic = AM.isAcyclic
Expand Down Expand Up @@ -324,6 +330,9 @@ adjacencyIntMapAPI = API
, dfsForest = coerce AIM.dfsForest
, dfsForestFrom = coerce AIM.dfsForestFrom
, dfs = coerce AIM.dfs
, bfsForest = coerce AIM.bfsForest
, bfsForestFrom = coerce AIM.bfsForestFrom
, bfs = coerce AIM.bfs
, reachable = coerce AIM.reachable
, topSort = coerce AIM.topSort
, isAcyclic = coerce AIM.isAcyclic
Expand Down
3 changes: 3 additions & 0 deletions test/Algebra/Graph/Test/AdjacencyIntMap.hs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ testAdjacencyIntMap = do
testDfsForest t
testDfsForestFrom t
testDfs t
testBfsForest t
jitwit marked this conversation as resolved.
Show resolved Hide resolved
testBfsForestFrom t
testBfs t
testReachable t
testTopSort t
testIsAcyclic t
Expand Down
3 changes: 3 additions & 0 deletions test/Algebra/Graph/Test/AdjacencyMap.hs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ testAdjacencyMap = do
testDfsForest t
testDfsForestFrom t
testDfs t
testBfsForest t
testBfsForestFrom t
testBfs t
testReachable t
testTopSort t
testIsAcyclic t
Expand Down
Loading