Graphs: a functional approach

Ivan Veselov

June 14, 2014

Outline

Two aspects of graph representation

  1. Representing graph structure

  2. Representing typical graph algorithms

Graph structure. Indirection

data Graph = Graph
  { nodes :: [Int]
  , edges :: [(Int, Int)]
  }

g1 = Graph
  { nodes = [1,2]
  , edges = [(1,2), (2,1)]
  }

Graph structure, another approach

data Node a = Node a [Node a]
data Graph a = Graph [Node a]

Graph structure

Graph algorithms

Inductive graphs idea

Types

type Node = Int
type Adj e = [(e, Node)]
type Context v e = (Adj e, Node, v, Adj e)

data Graph v e
  = Empty
  | Context v e :&: Graph v e

Context example

context of node 1 = ([(2, "e1")], 1, "A", [(4, "e4")])

Contexts order

([("left", 2), ("up", 3)], 1, 'a', [("right", 2)])
&
([], 2, 'b', [("down", 3)])
&
([], 3, 'c', [])
&
Empty

Pattern matching on graphs

isEmpty :: Graph v e -> Bool
isEmpty Empty = True
isEmpty _ = False

gmap :: (Context v1 e1 -> Context v2 e2) -> Graph v1 e1 -> Graph v2 e2
gmap _f Empty = Empty
gmap f (c :&: g) = f c :&: gmap f g

grev :: Graph v e -> Graph v e
grev = gmap swap where swap (i, n, v, o) = (o, n, v, i)

Proofs

grev (grev g) = g

gmap f . gmap g = gmap (f . g)             (1)
swap . swap = id                           (2)

Unordered fold and its usage

ufold :: (Context v e -> acc -> acc) -> acc -> Graph v e -> acc
ufold _ acc Empty = acc
ufold f acc (c :&: g) = f c (ufold f acc g)

gmap = ufold (\c -> (f c :&:)) Empty

nodes :: Graph v e -> [Node]
nodes = ufold (\(_,v,_,_) -> (v:)) []

undir :: Graph v e -> Graph v e
undir = gmap (\(p,v,l,s) -> let ps = nub (p ++ s) in (ps,v,l,ps))

Pattern matches

isEmpty :: Graph v e -> Bool

-- matchAny extracts any context from the graph
matchAny :: Graph v e -> (Context v e, Graph v e)

-- unordered graph handling idiom
gmap f g | isEmpty g = g
         | otherwise = f c & (gmap f g')
           where (c, g') = matchAny g

Ordered pattern matches

-- match decomposes a graph into a context for particular node and the rest of the graph
match :: Node -> Graph v e -> (Maybe (Context v e), Graph v e)

-- usage example
f v g = case match v g of
  (Nothing, g') -> e1
  (Just c, g')  -> e2

Example

Functional graph algorithms. DFS.

dfs :: [Node] -> Graph v e -> [Node]
dfs [] _ = []
dfs (v:vs) g = case match v g of
  (Just c, g') = v : dfs (suc c ++ vs) g'
  (Nothing, g') -> dfs vs g'

DFS to BFS.

dfs :: [Node] -> Graph v e -> [Node]
dfs [] _ = []
dfs (v:vs) g = case match v g of
  (Just c, g') = v : dfs (suc c ++ vs) g'
  (Nothing, g') -> dfs vs g'

DFS to BFS.

bfs :: [Node] -> Graph v e -> [Node]
bfs [] _ = []
bfs (v:vs) g = case match v g of
  (Just c, g') = v : bfs (vs ++ suc c) g'
  (Nothing, g') -> bfs vs g'

DFS to BFS.

bfs :: [Node] -> Graph v e -> [Node]
bfs [] _ = []
bfs (v:vs) g = case match v g of
  (Just c, g') -> v : bfs (vs ++ suc c) g'
  (Nothing, g') -> bfs vs g'

DFF

data Tree a = Br a [Tree a]

dff :: [Node] -> Graph v e -> [Tree Node]

DFF example

DFS-based algorithms

-- Toppological sort is a reversed post-order on DFS
topsort :: Graph v e -> [Node]
topsort = reverse . concatMap postOrder . dff

-- Kosaraju-Sharir algorithm for strongly connected components
scc :: Graph v e -> [Tree Node]
scc g = dff (topsort g) (grev g)

Complexity and internals

Other algorithms in FGL

FGL on hackage

Summary

Other graph libraries

References

  1. Martin Erwig. Inductive graphs and functional graph algorithms

  2. fgl on hackage

  3. StackOverflow: How do you represent a graph in Haskell?

  4. Algorithms course on Coursera (covers graph algorithms)

Thank you!