packages feed

crem-0.1.0.0: src/Crem/Graph.hs

-- | A simple data structure to describe a directed graph
module Crem.Graph where

import Crem.Render.RenderableVertices (RenderableVertices (..))
import "base" Data.List (nub)

-- * Graph

-- | A graph is just a list of edges between vertices of type @a@
newtype Graph a = Graph [(a, a)]
  deriving stock (Eq, Show)

-- | The product graph.
-- It has as vertices the product of the set of vertices of the initial graph.
-- It has as edge from @(a1, b1)@ to @(a2, b2)@ if and only if there is an edge
-- from @a1@ to @a2@ and an edge from @b1@ to @b2@
--
-- >>> productGraph (Graph [('a', 'b')]) (Graph [('c', 'd')])
-- Graph [(('a','c'),('b','d'))]
productGraph :: Graph a -> Graph b -> Graph (a, b)
productGraph (Graph edges1) (Graph edges2) =
  Graph $
    ( \((initialEdge1, finalEdge1), (initialEdge2, finalEdge2)) ->
        ((initialEdge1, initialEdge2), (finalEdge1, finalEdge2))
    )
      <$> [(edge1, edge2) | edge1 <- edges1, edge2 <- edges2]

-- | Computes all the possible paths in the input graph and considers them as
-- edges. Notice that the current implementation is removing duplicates
transitiveClosureGraph :: Eq a => Graph a -> Graph a
transitiveClosureGraph graph@(Graph edges) =
  Graph $
    foldr
      ( \a edgesSoFar ->
          edgesSoFar <> pathsFrom graph a
      )
      []
      (nub $ fst <$> edges)
  where
    edgesFrom :: Eq a => Graph a -> a -> [(a, a)]
    edgesFrom (Graph edges') a = filter ((== a) . fst) edges'

    pathsFrom :: forall a. Eq a => Graph a -> a -> [(a, a)]
    pathsFrom g a =
      let
        edgesFromAToB = edgesFrom g a
        pathsFromBToC = edgesFromAToB >>= pathsFrom g . snd
        edgesFromAToC = (a,) . snd <$> pathsFromBToC
       in
        edgesFromAToB <> edgesFromAToC

-- | Add all the identity edges to a graph
addIdentityEdges :: RenderableVertices a => Graph a -> Graph a
addIdentityEdges (Graph edges) = Graph $ edges <> ((\a -> (a, a)) <$> vertices)

-- | Remove all the edges which start and end at the same vertex
removeIdentityEdges :: Eq a => Graph a -> Graph a
removeIdentityEdges (Graph edges) = Graph $ filter (uncurry (/=)) edges

-- * UntypedGraph

-- | A data type to represent a graph which is not tracking the vertex type
data UntypedGraph
  = forall a. (RenderableVertices a, Eq a, Show a) => UntypedGraph (Graph a)

instance Show UntypedGraph where
  show :: UntypedGraph -> String
  show (UntypedGraph graph) = show graph

-- | Same as @productGraph@ but for @UntypedGraph@
untypedProductGraph :: UntypedGraph -> UntypedGraph -> UntypedGraph
untypedProductGraph (UntypedGraph graph1) (UntypedGraph graph2) =
  UntypedGraph (productGraph graph1 graph2)

-- | Same as @transitiveClosureGraph@ but for @UntypedGraph@
untypedTransitiveClosureGraph :: UntypedGraph -> UntypedGraph
untypedTransitiveClosureGraph (UntypedGraph graph) =
  UntypedGraph (transitiveClosureGraph graph)

-- | Add all identity edges to an @UntypedGraph@
untypedAddIdentityEdges :: UntypedGraph -> UntypedGraph
untypedAddIdentityEdges (UntypedGraph graph) =
  UntypedGraph $ addIdentityEdges graph

-- | Remove all the edges which start and end at the same vertex from an
-- @UntypedGraph@
untypedRemoveIdentityEdges :: UntypedGraph -> UntypedGraph
untypedRemoveIdentityEdges (UntypedGraph graph) =
  UntypedGraph $ removeIdentityEdges graph