Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
batterseapower committed Aug 11, 2010
0 parents commit c28cd41
Show file tree
Hide file tree
Showing 7 changed files with 367 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .ghci
@@ -0,0 +1,3 @@
:set -Wall -fno-warn-name-shadowing
:set -i.
:load Data.Graph.Wrapper
7 changes: 7 additions & 0 deletions .gitignore
@@ -0,0 +1,7 @@
# OS junk
.DS_Store
Thumbs.db

# Cabal junk
dist/

245 changes: 245 additions & 0 deletions Data/Graph/Wrapper.hs
@@ -0,0 +1,245 @@
-- | A wrapper around the types and functions from "Data.Graph" to make programming with them less painful. Also
-- implements some extra useful goodies such as 'successors' and 'sccGraph', and improves the documentation of
-- the behaviour of some functions.
--
-- As it wraps "Data.Graph", this module only supports directed graphs with unlabelled edges.
--
-- Incorporates code from the 'containers' package which is (c) The University of Glasgow 2002 and based
-- on code described in:
--
-- /Lazy Depth-First Search and Linear Graph Algorithms in Haskell/,
-- by David King and John Launchbury
module Data.Graph.Wrapper (
Edge, Graph,

vertex,

fromListSimple, fromList, fromListBy, fromVerticesEdges,

vertices, edges, successors,

outdegree, indegree,

transpose,

reachableVertices, hasPath,

topologicalSort,

SCC(..), stronglyConnectedComponents, sccGraph
) where

import Control.Arrow (second)

import Data.Array
import qualified Data.Graph as G
import Data.List (sortBy, mapAccumL)
import Data.Maybe (fromMaybe, fromJust)
import qualified Data.Map as M
import Data.Ord
import qualified Data.Set as S

import qualified Data.Foldable as Foldable
import qualified Data.Traversable as Traversable


fst3 :: (a, b, c) -> a
fst3 (a, _, _) = a

snd3 :: (a, b, c) -> b
snd3 (_, b, _) = b

thd3 :: (a, b, c) -> c
thd3 (_, _, c) = c


-- | An edge from the first vertex to the second
type Edge i = (i, i)

-- | A directed graph
data Graph i v = G {
graph :: G.Graph,
indexGVertexArray :: Array G.Vertex i,
gVertexVertexArray :: Array G.Vertex v
}

instance (Ord i, Show i, Show v) => Show (Graph i v) where
show g = "fromVerticesEdges " ++ show ([(i, vertex g i) | i <- vertices g]) ++ " " ++ show (edges g)

instance Functor (Graph i) where
fmap f g = g { gVertexVertexArray = fmap f (gVertexVertexArray g) }

instance Foldable.Foldable (Graph i) where
foldMap f g = Foldable.foldMap f (gVertexVertexArray g)

instance Traversable.Traversable (Graph i) where
traverse f g = fmap (\gvva -> g { gVertexVertexArray = gvva }) (Traversable.traverse f (gVertexVertexArray g))


indexGVertex :: Ord i => Graph i v -> i -> G.Vertex
indexGVertex g i = indexGVertex' (indexGVertexArray g) i

gVertexIndex :: Graph i v -> G.Vertex -> i
gVertexIndex g gv = indexGVertexArray g ! gv

gVertexVertex :: Graph i v -> G.Vertex -> v
gVertexVertex g gv = gVertexVertexArray g ! gv

-- | Retrieve data associated with the vertex
vertex :: Ord i => Graph i v -> i -> v
vertex g = gVertexVertex g . indexGVertex g


-- | Construct a 'Graph' where the vertex data double up as the indices.
--
-- Unlike 'Data.Graph.graphFromEdges', vertex data that is listed as edges that are not actually themselves
-- present in the input list are reported as an error.
fromListSimple :: Ord v => [(v, [v])] -> Graph v v
fromListSimple = fromListBy id

-- | Construct a 'Graph' that contains the given vertex data, linked up according to the supplied key extraction
-- function and edge list.
--
-- Unlike 'Data.Graph.graphFromEdges', indexes in the edge list that do not correspond to the index of some item in the
-- input list are reported as an error.
fromListBy :: Ord i => (v -> i) -> [(v, [i])] -> Graph i v
fromListBy f vertices = fromList [(f v, v, is) | (v, is) <- vertices]

-- | Construct a 'Graph' directly from a list of vertices (and vertex data).
--
-- If either end of an 'Edge' does not correspond to a supplied vertex, an error will be raised.
fromVerticesEdges :: Ord i => [(i, v)] -> [Edge i] -> Graph i v
fromVerticesEdges vertices edges | M.null final_edges_map = fromList done_vertices
| otherwise = error "fromVerticesEdges: some edges originated from non-existant vertices"
where
(final_edges_map, done_vertices) = mapAccumL accum (M.fromListWith (++) (map (second return) edges)) vertices
accum edges_map (i, v) = case M.updateLookupWithKey (\_ _ -> Nothing) i edges_map of (mb_is, edges_map) -> (edges_map, (i, v, fromMaybe [] mb_is))

-- | Construct a 'Graph' that contains the given vertex data, linked up according to the supplied index and edge list.
--
-- Unlike 'Data.Graph.graphFromEdges', indexes in the edge list that do not correspond to the index of some item in the
-- input list are reported as an error.
fromList :: Ord i => [(i, v, [i])] -> Graph i v
fromList vertices = G graph key_map vertex_map
where
max_v = length vertices - 1
bounds0 = (0, max_v) :: (G.Vertex, G.Vertex)
sorted_vertices = sortBy (comparing fst3) vertices

graph = array bounds0 $ [0..] `zip` map (map (indexGVertex' key_map) . thd3) sorted_vertices
key_map = array bounds0 $ [0..] `zip` map fst3 sorted_vertices
vertex_map = array bounds0 $ [0..] `zip` map snd3 sorted_vertices

indexGVertex' :: Ord i => Array G.Vertex i -> i -> G.Vertex
indexGVertex' key_map k = go 0 (snd (bounds key_map))
where
go a b | a > b = error "Data.Graph.Wrapper.fromList: one of the edges of a vertex pointed to a vertex that was not supplied in the input"
| otherwise = case compare k (key_map ! mid) of
LT -> go a (mid - 1)
EQ -> mid
GT -> go (mid + 1) b
where mid = (a + b) `div` 2

-- | Exhaustive list of vertices in the graph
vertices :: Graph i v -> [i]
vertices g = map (gVertexIndex g) $ G.vertices (graph g)

-- | Exhaustive list of edges in the graph
edges :: Graph i v -> [Edge i]
edges g = map (\(x, y) -> (gVertexIndex g x, gVertexIndex g y)) $ G.edges (graph g)

-- | Find the vertices we can reach from a vertex with the given indentity
successors :: Ord i => Graph i v -> i -> [i]
successors g i = map (gVertexIndex g) (graph g ! indexGVertex g i)

-- | Number of edges going out of the vertex.
--
-- It is worth sharing a partial application of 'outdegree' to the 'Graph' argument if you intend to query
-- for the outdegrees of a number of vertices.
outdegree :: Ord i => Graph i v -> i -> Int
outdegree g = \i -> outdegrees ! indexGVertex g i
where outdegrees = G.outdegree (graph g)

-- | Number of edges going in to the vertex.
--
-- It is worth sharing a partial application of 'indegree' to the 'Graph' argument if you intend to query
-- for the indegrees of a number of vertices.
indegree :: Ord i => Graph i v -> i -> Int
indegree g = \i -> indegrees ! indexGVertex g i
where indegrees = G.indegree (graph g)

-- | The graph formed by flipping all the edges, so edges from i to j now go from j to i
transpose :: Graph i v -> Graph i v
transpose g = g { graph = G.transposeG (graph g) }

-- | Topological sort of of the graph (<http://en.wikipedia.org/wiki/Topological_sort>). If the graph is acyclic,
-- vertices will only appear in the list once all of those vertices with arrows to them have already appeared.
--
-- Vertex i precedes j in the output whenever j is reachable from i but not vice versa.
topologicalSort :: Graph i v -> [i]
topologicalSort g = map (gVertexIndex g) $ G.topSort (graph g)

-- | List all of the vertices reachable from the given starting point
reachableVertices :: Ord i => Graph i v -> i -> [i]
reachableVertices g = map (gVertexIndex g) . G.reachable (graph g) . indexGVertex g

-- | Is the second vertex reachable by following edges from the first vertex?
hasPath :: Ord i => Graph i v -> Edge i -> Bool
hasPath g (i1, i2) = G.path (graph g) (indexGVertex g i1) (indexGVertex g i2)


data SCC i = AcyclicSCC i
| CyclicSCC [i]
deriving (Show)

instance Functor SCC where
fmap f (AcyclicSCC v) = AcyclicSCC (f v)
fmap f (CyclicSCC vs) = CyclicSCC (map f vs)

instance Foldable.Foldable SCC where
foldMap f (AcyclicSCC v) = f v
foldMap f (CyclicSCC vs) = Foldable.foldMap f vs

instance Traversable.Traversable SCC where
traverse f (AcyclicSCC v) = fmap AcyclicSCC (f v)
traverse f (CyclicSCC vs) = fmap CyclicSCC (Traversable.traverse f vs)

-- | Strongly connected components (<http://en.wikipedia.org/wiki/Strongly_connected_component>).
--
-- The SCCs are listed in a *reverse topological order*. That is to say, any edges *to* a node in the SCC
-- originate either *from*:
--
-- 1) Within the SCC itself (in the case of a 'CyclicSCC' only)
-- 2) Or from a node in a SCC later on in the list
--
-- Vertex i strictly precedes j in the output whenever i is reachable from j but not vice versa.
-- Vertex i occurs in the same SCC as j whenever both i is reachable from j and j is reachable from i.
stronglyConnectedComponents :: Graph i v -> [SCC i]
stronglyConnectedComponents g = map decode forest
where
forest = G.scc (graph g)
decode (G.Node v []) | mentions_itself v = CyclicSCC [gVertexIndex g v]
| otherwise = AcyclicSCC (gVertexIndex g v)
decode other = CyclicSCC (dec other [])
where dec (G.Node v ts) vs = gVertexIndex g v : foldr dec vs ts

mentions_itself v = v `elem` (graph g ! v)

-- | The graph formed by the strongly connected components of the input graph. Each node in the resulting
-- graph is indexed by the set of vertex indices from the input graph that it contains.
sccGraph :: Ord i => Graph i v -> Graph (S.Set i) (M.Map i v)
sccGraph g = fromList nodes'
where
-- As we consume the SCCs, we accumulate a Map i (S.Set i) that tells us which SCC any given index belongs to.
-- When we do a lookup, it is sufficient to look in the map accumulated so far because nodes that are successors
-- of a SCC must occur to the *left* of it in the list.
(_final_i2scc_i, nodes') = mapAccumL go M.empty (stronglyConnectedComponents g)

--go :: M.Map i (S.Set i) -> SCC i -> (M.Map i (S.Set i), (S.Set i, M.Map i v, [S.Set i]))
go i2scc_i scc = (i2scc_i', (scc_i,
Foldable.foldMap (\i -> M.singleton i (vertex g i)) scc,
Foldable.foldMap (\i -> map (fromJust . (`M.lookup` i2scc_i')) (successors g i)) scc))
where
-- The mechanism by which we index the new graph -- the set of indexes of its components
scc_i = Foldable.foldMap S.singleton scc
i2scc_i' = i2scc_i `M.union` Foldable.foldMap (\i -> M.singleton i scc_i) scc
30 changes: 30 additions & 0 deletions LICENSE
@@ -0,0 +1,30 @@
Copyright Max Bolingbroke 2006-2007.
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.

* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.

* Neither the name of Max Bolingbroke nor the names of other
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
3 changes: 3 additions & 0 deletions Setup.hs
@@ -0,0 +1,3 @@
import Distribution.Simple

main = defaultMain
18 changes: 18 additions & 0 deletions graph-wrapper.cabal
@@ -0,0 +1,18 @@
Cabal-Version: >= 1.2
Build-Type: Simple
Name: graph-wrapper
Version: 0.1
Maintainer: Max Bolingbroke <batterseapower@hotmail.com>
Homepage: http://www.github.com/batterseapower/graph-wrapper
License: BSD3
License-File: LICENSE
Author: Max Bolingbroke
Synopsis: A wrapper around the standard Data.Graph with a less awkward interface
Category: Data Structures

Library
Exposed-Modules: Data.Graph.Wrapper

Build-Depends: base >= 3.0 && < 5.0,
array >= 0.3 && < 0.4,
containers >= 0.3 && < 0.4
61 changes: 61 additions & 0 deletions release
@@ -0,0 +1,61 @@
#!/bin/bash
#

echo "Have you updated the version number? Type 'yes' if you have!"
read version_response

if [ "$version_response" != "yes" ]; then
echo "Go and update the version number"
exit 1
fi

sdist_output=`runghc Setup.lhs sdist`

if [ "$?" != "0" ]; then
echo "Cabal sdist failed, aborting"
exit 1
fi

# Want to find a line like:
# Source tarball created: dist/ansi-terminal-0.1.tar.gz

# Test this with:
# runghc Setup.lhs sdist | grep ...
filename=`echo $sdist_output | sed 's/.*Source tarball created: \([^ ]*\).*/\1/'`
echo "Filename: $filename"

if [ "$filename" = "$sdist_output" ]; then
echo "Could not find filename, aborting"
exit 1
fi

# Test this with:
# echo dist/ansi-terminal-0.1.tar.gz | sed ...
version=`echo $filename | sed 's/^[^0-9]*\([0-9\.]*\).tar.gz$/\1/'`
echo "Version: $version"

if [ "$version" = "$filename" ]; then
echo "Could not find version, aborting"
exit 1
fi

echo "This is your last chance to abort! I'm going to upload in 10 seconds"
sleep 10

git tag "v$version"

if [ "$?" != "0" ]; then
echo "Git tag failed, aborting"
exit 1
fi

# You need to have stored your Hackage username and password in ~/.cabal/config
cabal upload $filename

if [ "$?" != "0" ]; then
echo "Hackage upload failed, aborting"
exit 1
fi

# Success!
exit 0

0 comments on commit c28cd41

Please sign in to comment.