Skip to content

Commit ab93143

Browse files
authored
Rewrite (#5)
* Use foreachE to avoid stack overflow for large graphs * Rewrite: remove SCC calculation, add much faster topSort implementation * More rewriting * Defunctionalize * Simplify types
1 parent 70091b7 commit ab93143

File tree

3 files changed

+118
-140
lines changed

3 files changed

+118
-140
lines changed

bower.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@
1919
"package.json"
2020
],
2121
"dependencies": {
22-
"purescript-sets": "^1.0.0"
22+
"purescript-sets": "^2.0.0",
23+
"purescript-catenable-lists": "^3.0.0"
24+
},
25+
"devDependencies": {
26+
"purescript-console": "^2.0.0"
2327
}
2428
}

src/Data/Graph.purs

Lines changed: 99 additions & 139 deletions
Original file line numberDiff line numberDiff line change
@@ -1,152 +1,112 @@
11
-- | A data structure and functions for graphs
22

33
module Data.Graph
4-
( Edge(..)
5-
, Graph(..)
6-
, SCC(..)
4+
( Graph
5+
, unfoldGraph
6+
, fromMap
77
, vertices
8-
, scc
9-
, scc'
10-
, topSort
11-
, topSort'
8+
, lookup
9+
, outEdges
10+
, topologicalSort
1211
) where
1312

14-
import Prelude hiding
15-
16-
import Control.Monad.Eff (runPure)
17-
import Control.Monad.ST (writeSTRef, modifySTRef, readSTRef, newSTRef, runST)
18-
19-
import Data.Foldable (any, for_, elem)
20-
import Data.List (List(..), concatMap, reverse, singleton)
13+
import Prelude
14+
import Data.Bifunctor (lmap)
15+
import Data.CatList (CatList)
16+
import Data.CatList as CL
17+
import Data.Foldable (class Foldable)
18+
import Data.List (List(..))
19+
import Data.List as L
20+
import Data.Map (Map)
2121
import Data.Map as M
22-
import Data.Maybe (Maybe(..), isNothing)
23-
import Data.Traversable (for)
24-
25-
-- | An directed edge between vertices labelled with keys of type `k`.
26-
data Edge k = Edge k k
22+
import Data.Maybe (Maybe(..), maybe)
23+
import Data.Monoid (mempty)
24+
import Data.Tuple (Tuple(..), fst, snd)
2725

2826
-- | A graph with vertices of type `v`.
2927
-- |
3028
-- | Edges refer to vertices using keys of type `k`.
31-
data Graph k v = Graph (List v) (List (Edge k))
32-
33-
-- | A strongly-connected component of a graph.
29+
newtype Graph k v = Graph (Map k (Tuple v (List k)))
30+
31+
instance functorGraph :: Functor (Graph k) where
32+
map f (Graph m) = Graph (map (lmap f) m)
33+
34+
-- | Unfold a `Graph` from a collection of keys and functions which label keys
35+
-- | and specify out-edges.
36+
unfoldGraph
37+
:: forall f k v out
38+
. (Ord k, Functor f, Foldable f, Foldable out)
39+
=> f k
40+
-> (k -> v)
41+
-> (k -> out k)
42+
-> Graph k v
43+
unfoldGraph ks label edges =
44+
Graph (M.fromFoldable (map (\k ->
45+
Tuple k (Tuple (label k) (L.fromFoldable (edges k)))) ks))
46+
47+
-- | Create a `Graph` from a `Map` which maps vertices to their labels and
48+
-- | outgoing edges.
49+
fromMap :: forall k v. Map k (Tuple v (List k)) -> Graph k v
50+
fromMap = Graph
51+
52+
-- | List all vertices in a graph.
53+
vertices :: forall k v. Graph k v -> List v
54+
vertices (Graph g) = map fst (M.values g)
55+
56+
-- | Lookup a vertex by its key.
57+
lookup :: forall k v. Ord k => k -> Graph k v -> Maybe v
58+
lookup k (Graph g) = map fst (M.lookup k g)
59+
60+
-- | Get the keys which are directly accessible from the given key.
61+
outEdges :: forall k v. Ord k => k -> Graph k v -> Maybe (List k)
62+
outEdges k (Graph g) = map snd (M.lookup k g)
63+
64+
type SortState k v =
65+
{ unvisited :: Map k (Tuple v (List k))
66+
, result :: List k
67+
}
68+
69+
-- To defunctionalize the `topologicalSort` function and make it tail-recursive,
70+
-- we introduce this data type which captures what we intend to do at each stage
71+
-- of the recursion.
72+
data SortStep a = Emit a | Visit a
73+
74+
-- | Topologically sort the vertices of a graph.
3475
-- |
35-
-- | - `AcyclicSCC` identifies strongly-connected components consisting of a single vertex.
36-
-- | - `CyclicSCC` identifies strongly-connected components with one or more vertices with
37-
-- | cycles.
38-
data SCC v = AcyclicSCC v | CyclicSCC (List v)
39-
40-
instance showSCC :: (Show v) => Show (SCC v) where
41-
show (AcyclicSCC v) = "(AcyclicSCC " <> show v <> ")"
42-
show (CyclicSCC vs) = "(CyclicSCC " <> show vs <> ")"
43-
44-
instance eqSCC :: (Eq v) => Eq (SCC v) where
45-
eq (AcyclicSCC v1) (AcyclicSCC v2) = v1 == v2
46-
eq (CyclicSCC vs1) (CyclicSCC vs2) = vs1 == vs2
47-
eq _ _ = false
48-
49-
-- | Returns the vertices contained in a strongly-connected component.
50-
vertices :: forall v. SCC v -> List v
51-
vertices (AcyclicSCC v) = singleton v
52-
vertices (CyclicSCC vs) = vs
53-
54-
-- | Compute the strongly connected components of a graph.
55-
scc :: forall v. Ord v => Graph v v -> List (SCC v)
56-
scc = scc' id id
57-
58-
-- | Compute the strongly connected components of a graph.
59-
-- |
60-
-- | This function is a slight generalization of `scc` which allows key and value types
61-
-- | to differ.
62-
scc' :: forall k v. Ord k => (v -> k) -> (k -> v) -> Graph k v -> List (SCC v)
63-
scc' makeKey makeVert (Graph vs es) = runPure (runST (do
64-
index <- newSTRef zero
65-
path <- newSTRef Nil
66-
indexMap <- newSTRef M.empty
67-
lowlinkMap <- newSTRef M.empty
68-
components <- newSTRef Nil
69-
70-
(let
71-
indexOf v = indexOfKey (makeKey v)
72-
73-
indexOfKey k = do
74-
m <- readSTRef indexMap
75-
pure $ M.lookup k m
76-
77-
lowlinkOf v = lowlinkOfKey (makeKey v)
78-
79-
lowlinkOfKey k = do
80-
m <- readSTRef lowlinkMap
81-
pure $ M.lookup k m
82-
83-
go Nil = readSTRef components
84-
go (Cons v vs) = do
85-
currentIndex <- indexOf v
86-
when (isNothing currentIndex) $ strongConnect (makeKey v)
87-
go vs
88-
89-
strongConnect k = do
90-
let v = makeVert k
91-
92-
i <- readSTRef index
93-
94-
modifySTRef indexMap $ M.insert k i
95-
modifySTRef lowlinkMap $ M.insert k i
96-
97-
writeSTRef index $ i + one
98-
modifySTRef path $ Cons v
99-
100-
for es $ \(Edge k' l) -> when (k == k') $ do
101-
wIndex <- indexOfKey l
102-
currentPath <- readSTRef path
103-
104-
case wIndex of
105-
Nothing -> do
106-
let w = makeVert l
107-
strongConnect l
108-
wLowlink <- lowlinkOfKey l
109-
for_ wLowlink $ \lowlink ->
110-
modifySTRef lowlinkMap $ M.alter (maybeMin lowlink) k
111-
_ -> when (l `elem` map makeKey currentPath) $ do
112-
wIndex' <- indexOfKey l
113-
for_ wIndex' $ \index' ->
114-
modifySTRef lowlinkMap $ M.alter (maybeMin index') k
115-
116-
vIndex <- indexOfKey k
117-
vLowlink <- lowlinkOfKey k
118-
119-
when (vIndex == vLowlink) $ do
120-
currentPath <- readSTRef path
121-
let newPath = popUntil makeKey v currentPath Nil
122-
modifySTRef components $ flip (<>) (singleton (makeComponent newPath.component))
123-
writeSTRef path newPath.path
124-
pure unit
125-
126-
makeComponent (Cons v Nil) | not (isCycle (makeKey v)) = AcyclicSCC v
127-
makeComponent vs = CyclicSCC vs
128-
129-
isCycle k = any (\(Edge k1 k2) -> k1 == k && k2 == k) es
130-
in go vs)))
131-
132-
popUntil :: forall k v. Eq k => (v -> k) -> v -> List v -> List v -> { path :: List v, component :: List v }
133-
popUntil _ _ Nil popped = { path: Nil, component: popped }
134-
popUntil makeKey v (Cons w path) popped | makeKey v == makeKey w = { path: path, component: Cons w popped }
135-
popUntil makeKey v (Cons w ws) popped = popUntil makeKey v ws (Cons w popped)
136-
137-
maybeMin :: Int -> Maybe Int -> Maybe Int
138-
maybeMin i Nothing = Just i
139-
maybeMin i (Just j) = Just $ min i j
76+
-- | If the graph contains cycles, then the behavior is undefined.
77+
topologicalSort :: forall k v. Ord k => Graph k v -> List k
78+
topologicalSort (Graph g) =
79+
go initialState
14080
where
141-
min x y = if x < y then x else y
142-
143-
-- | Topologically sort the vertices of a graph
144-
topSort :: forall v. Ord v => Graph v v -> List v
145-
topSort = topSort' id id
146-
147-
-- | Topologically sort the vertices of a graph
148-
-- |
149-
-- | This function is a slight generalization of `scc` which allows key and value types
150-
-- | to differ.
151-
topSort' :: forall k v. Ord k => (v -> k) -> (k -> v) -> Graph k v -> List v
152-
topSort' makeKey makeVert = reverse <<< concatMap vertices <<< scc' makeKey makeVert
81+
go :: SortState k v -> List k
82+
go state@{ unvisited, result } =
83+
case M.findMin unvisited of
84+
Just { key } -> go (visit state (CL.fromFoldable [Visit key]))
85+
Nothing -> result
86+
87+
visit :: SortState k v -> CatList (SortStep k) -> SortState k v
88+
visit state stack =
89+
case CL.uncons stack of
90+
Nothing -> state
91+
Just (Tuple (Emit k) ks) ->
92+
let state' = { result: Cons k state.result
93+
, unvisited: state.unvisited
94+
}
95+
in visit state' ks
96+
Just (Tuple (Visit k) ks)
97+
| k `M.member` state.unvisited ->
98+
let start :: SortState k v
99+
start =
100+
{ result: state.result
101+
, unvisited: M.delete k state.unvisited
102+
}
103+
104+
next :: List k
105+
next = maybe mempty snd (M.lookup k g)
106+
in visit start (CL.fromFoldable (map Visit next) <> CL.cons (Emit k) ks)
107+
| otherwise -> visit state ks
108+
109+
initialState :: SortState k v
110+
initialState = { unvisited: g
111+
, result: Nil
112+
}

test/Main.purs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
module Test.Main where
2+
3+
import Prelude
4+
import Control.Monad.Eff (foreachE, Eff)
5+
import Control.Monad.Eff.Console (CONSOLE, logShow)
6+
import Data.Graph (unfoldGraph, topologicalSort)
7+
import Data.List (toUnfoldable, range)
8+
9+
main :: Eff (console :: CONSOLE) Unit
10+
main = do
11+
let double x | x * 2 < 100000 = [x * 2]
12+
| otherwise = []
13+
graph = unfoldGraph (range 1 100000) id double
14+
foreachE (toUnfoldable (topologicalSort graph)) logShow

0 commit comments

Comments
 (0)