{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE DeriveFoldable #-}
{-# LANGUAGE DeriveTraversable #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
module Data.DAG
(
DAG
, NodeID (..)
, EdgeID (..)
, Edge (..)
, begsWith
, endsWith
, ingoingEdges
, outgoingEdges
, maybeNodeLabel
, nodeLabel
, maybeEdgeLabel
, edgeLabel
, prevEdges
, isInitialEdge
, nextEdges
, isFinalEdge
, minEdge
, maxEdge
, mapN
, mapE
, zipE
, zipE'
, dagNodes
, dagEdges
, fromList
, fromList'
, fromEdgesUnsafe
, splitTmp
, filterDAG
, isOK
, isDAG
, topoSort
) where
import Control.Applicative ((<|>))
import Control.Arrow (first)
import Control.Monad (guard)
import qualified Data.Foldable as F
import qualified Data.List as L
import Data.Maybe (isJust)
import qualified Data.Traversable as T
import qualified Data.Array as A
import qualified Data.Set as S
import qualified Data.Map.Strict as M
import Data.Binary (Binary, get, put)
data DAG a b = DAG
{ nodeMap :: M.Map NodeID (Node a)
, edgeMap :: M.Map EdgeID (Edge b)
} deriving (Functor, F.Foldable, T.Traversable)
instance (Binary a, Binary b) => Binary (DAG a b) where
put = undefined
get = undefined
newtype NodeID = NodeID {unNodeID :: Int}
deriving (Show, Eq, Ord)
data Node a = Node
{ ingoSet :: S.Set EdgeID
, outgoSet :: S.Set EdgeID
, ndLabel :: a }
deriving (Show, Eq, Ord)
newtype EdgeID = EdgeID {unEdgeID :: Int}
deriving (Show, Eq, Ord, Num, A.Ix)
data Edge a = Edge
{ tailNode :: NodeID
, headNode :: NodeID
, edLabel :: a }
deriving (Show, Eq, Ord, Functor, F.Foldable, T.Traversable)
edgeOn :: EdgeID -> DAG a b -> Edge b
edgeOn i DAG{..} = case M.lookup i edgeMap of
Nothing -> error "edgeWith: incorrent edge ID"
Just edge -> edge
begsWith :: EdgeID -> DAG a b -> NodeID
begsWith i DAG{..} = case M.lookup i edgeMap of
Nothing -> error "begsWith: incorrent edge ID"
Just Edge{..} -> tailNode
endsWith :: EdgeID -> DAG a b -> NodeID
endsWith i DAG{..} = case M.lookup i edgeMap of
Nothing -> error "endsWith: incorrent edge ID"
Just Edge{..} -> headNode
ingoingEdges :: NodeID -> DAG a b -> [EdgeID]
ingoingEdges i DAG{..} = case M.lookup i nodeMap of
Nothing -> error "ingoingEdges: incorrect ID"
Just Node{..} -> S.toAscList ingoSet
outgoingEdges :: NodeID -> DAG a b -> [EdgeID]
outgoingEdges i DAG{..} = case M.lookup i nodeMap of
Nothing -> error "outgoingEdges: incorrect ID"
Just Node{..} -> S.toAscList outgoSet
maybeNodeLabel :: NodeID -> DAG a b -> Maybe a
maybeNodeLabel i DAG{..} = ndLabel <$> M.lookup i nodeMap
nodeLabel :: NodeID -> DAG a b -> a
nodeLabel i DAG{..} = case M.lookup i nodeMap of
Nothing -> error "nodeLabel: incorrect ID"
Just Node{..} -> ndLabel
maybeEdgeLabel :: EdgeID -> DAG a b -> Maybe b
maybeEdgeLabel i DAG{..} = edLabel <$> M.lookup i edgeMap
edgeLabel :: EdgeID -> DAG a b -> b
edgeLabel i DAG{..} = case M.lookup i edgeMap of
Nothing -> error "edgeLabel: incorrent ID"
Just Edge{..} -> edLabel
minEdge :: DAG a b -> EdgeID
minEdge = fst . M.findMin . edgeMap
maxEdge :: DAG a b -> EdgeID
maxEdge = fst . M.findMax . edgeMap
dagNodes :: DAG a b -> [NodeID]
dagNodes = M.keys . nodeMap
mapN :: (a -> b) -> DAG a c -> DAG b c
mapN f dag =
dag {nodeMap = nodeMap'}
where
nodeMap' = M.fromList
[ (nodeID, node {ndLabel = newLabel})
| (nodeID, node) <- M.toList (nodeMap dag)
, let newLabel = f (ndLabel node) ]
dagEdges :: DAG a b -> [EdgeID]
dagEdges = M.keys . edgeMap
mapE :: (EdgeID -> b -> c) -> DAG a b -> DAG a c
mapE f dag =
dag {edgeMap = edgeMap'}
where
edgeMap' = M.fromList
[ (edgeID, edge {edLabel = newLabel})
| (edgeID, edge) <- M.toList (edgeMap dag)
, let newLabel = f edgeID (edLabel edge) ]
zipE :: DAG a b -> DAG x c -> DAG a (b, c)
zipE dagL dagR
| M.keysSet (nodeMap dagL) /= M.keysSet (nodeMap dagR) =
error "zipE: different sets of node IDs"
| M.keysSet (edgeMap dagL) /= M.keysSet (edgeMap dagR) =
error "zipE: different sets of edge IDs"
| otherwise = DAG
{ nodeMap = newNodeMap
, edgeMap = newEdgeMap }
where
newNodeMap = nodeMap dagL
newEdgeMap = M.fromList
[ (edgeID, newEdge)
| edgeID <- M.keys (edgeMap dagL)
, let newEdge = mergeEdges
(edgeMap dagL M.! edgeID)
(edgeMap dagR M.! edgeID) ]
mergeEdges e1 e2
| tailNode e1 /= tailNode e2 =
error "zipE.mergEdges: different tail nodes"
| headNode e1 /= headNode e2 =
error "zipE.mergEdges: different head nodes"
| otherwise =
let newLabel = (edLabel e1, edLabel e2)
in e1 {edLabel = newLabel}
zipE' :: DAG x a -> DAG y b -> DAG () (Maybe a, Maybe b)
zipE' dagL dagR
= fromEdgesUnsafe newEdgeList
where
edgesIn dag = map (flip edgeOn dag) (dagEdges dag)
reconcile (x1, y1) (x2, y2) = (x1 <|> x2, y1 <|> y2)
newEdgeMap = M.fromListWith reconcile $
[ ( (tailNode edge, headNode edge)
, (Just (edLabel edge), Nothing) )
| edge <- edgesIn dagL ] ++
[ ( (tailNode edge, headNode edge)
, (Nothing, Just (edLabel edge)) )
| edge <- edgesIn dagR ]
newEdgeList =
[ Edge {tailNode = from, headNode = to, edLabel = label}
| ((from, to), label) <- M.toList newEdgeMap ]
prevEdges :: EdgeID -> DAG a b -> [EdgeID]
prevEdges edgeID dag =
let tailNodeID = begsWith edgeID dag
in ingoingEdges tailNodeID dag
isInitialEdge :: EdgeID -> DAG a b -> Bool
isInitialEdge edgeID = null . prevEdges edgeID
nextEdges :: EdgeID -> DAG a b -> [EdgeID]
nextEdges edgeID dag =
let headNodeID = endsWith edgeID dag
in outgoingEdges headNodeID dag
isFinalEdge :: EdgeID -> DAG a b -> Bool
isFinalEdge edgeID = null . nextEdges edgeID
_fromList :: a -> [(a, b)] -> DAG a b
_fromList nodeLabel0 xs = DAG
{ nodeMap = newNodeMap
, edgeMap = newEdgeMap }
where
newNodeMap = M.fromList $ do
let nodeLabels = nodeLabel0 : map fst xs
xsLength = length xs
(i, y) <- zip [0 .. length xs] nodeLabels
let node = Node
{ ingoSet =
if i > 0
then S.singleton $ EdgeID (i-1)
else S.empty
, outgoSet =
if i < xsLength
then S.singleton $ EdgeID i
else S.empty
, ndLabel = y }
return (NodeID i, node)
newEdgeMap = M.fromList $ do
(i, x) <- zip [0..] (map snd xs)
let edge = Edge
{ tailNode = NodeID i
, headNode = NodeID (i+1)
, edLabel = x }
return (EdgeID i, edge)
fromList :: [a] -> DAG () a
fromList xs =
if isOK dag
then dag
else error "fromList: resulting DAG not `isOK`"
where
dag = _fromList () $ zip (repeat ()) xs
fromList' :: a -> [(a, b)] -> DAG a b
fromList' x xs =
if isOK dag
then dag
else error "fromList': resulting DAG not `isOK`"
where
dag = _fromList x xs
_fromEdgesUnsafe :: [Edge a] -> DAG () a
_fromEdgesUnsafe edges = DAG
{ nodeMap = newNodeMap
, edgeMap = newEdgeMap }
where
newEdgeMap = M.fromList $ do
(i, edge) <- zip [0..] edges
return (EdgeID i, edge)
tailMap = M.fromListWith S.union $ do
(i, edge) <- zip [0..] edges
return (tailNode edge, S.singleton $ EdgeID i)
headMap = M.fromListWith S.union $ do
(i, edge) <- zip [0..] edges
return (headNode edge, S.singleton $ EdgeID i)
newNodeMap = M.fromList $ do
nodeID <- S.toList $ S.union (M.keysSet headMap) (M.keysSet tailMap)
let ingo = case M.lookup nodeID headMap of
Nothing -> S.empty
Just st -> st
ougo = case M.lookup nodeID tailMap of
Nothing -> S.empty
Just st -> st
node = Node
{ ingoSet = ingo
, outgoSet = ougo
, ndLabel = () }
return (nodeID, node)
fromEdgesUnsafe :: [Edge a] -> DAG () a
fromEdgesUnsafe xs =
if isOK dag
then dag
else error "fromEdgesUnsafe: resulting DAG not `isOK`"
where
dag = _fromEdgesUnsafe xs
splitTmp :: NodeID -> DAG a b -> Maybe (DAG a b, DAG a b)
splitTmp splitNodeID dag
| isOK dagLeft && isOK dagRight = Just (dagLeft, dagRight)
| otherwise = Nothing
where
dagLeft = DAG nodesLeft edgesLeft
dagRight = DAG nodesRight edgesRight
edgesLeft = M.fromList
[ (edgeID, edge)
| (edgeID, edge) <- M.toList (edgeMap dag)
, endsWith edgeID dag <= splitNodeID
]
nodesLeft = M.fromList
[ (nodeID, trim node)
| (nodeID, node) <- M.toList (nodeMap dag)
, nodeID <= splitNodeID ]
where trim = trimNode (M.keysSet edgesLeft)
edgesRight = M.fromList
[ (edgeID, edge)
| (edgeID, edge) <- M.toList (edgeMap dag)
, begsWith edgeID dag >= splitNodeID
]
nodesRight = M.fromList
[ (nodeID, trim node)
| (nodeID, node) <- M.toList (nodeMap dag)
, nodeID >= splitNodeID ]
where trim = trimNode (M.keysSet edgesRight)
trimNode edgeSet = trimIngo edgeSet . trimOutgo edgeSet
trimIngo edgeSet node =
node {ingoSet = ingoSet node `S.intersection` edgeSet}
trimOutgo edgeSet node =
node {outgoSet = outgoSet node `S.intersection` edgeSet}
filterDAG :: S.Set EdgeID -> DAG a b -> DAG a b
filterDAG edgeSet DAG{..} =
DAG newNodeMap newEdgeMap
where
newEdgeMap = M.fromList $ do
(edgeID, edge) <- M.toList edgeMap
guard $ edgeID `S.member` edgeSet
return (edgeID, edge)
newNodeMap = M.fromList $ do
(nodeID, node) <- M.toList nodeMap
Just newNode <- return $ updNode node
return (nodeID, newNode)
updNode nd
| S.null newIngoSet && S.null newOutgoSet = Nothing
| otherwise = Just $ nd
{ ingoSet = newIngoSet
, outgoSet = newOutgoSet }
where
newIngoSet = ingoSet nd `S.intersection` edgeSet
newOutgoSet = outgoSet nd `S.intersection` edgeSet
isOK :: DAG a b -> Bool
isOK DAG{..} =
nodeMapOK && edgeMapOK
where
nodeMapOK = and
[ M.member edgeID edgeMap
| (_nodeID, Node{..}) <- M.toList nodeMap
, edgeID <- S.toList (S.union ingoSet outgoSet) ]
edgeMapOK = and
[ M.member nodeID nodeMap
| (_edgeID, Edge{..}) <- M.toList edgeMap
, nodeID <- [tailNode, headNode] ]
isDAG :: DAG a b -> Bool
isDAG = isJust . topoSort
topoSort :: DAG a b -> Maybe [NodeID]
topoSort dag0 =
go dag0 $ S.fromList
[ nodeID | nodeID <- dagNodes dag0
, null $ ingoingEdges nodeID dag0 ]
where
go dag noIncoming =
case S.minView noIncoming of
Just (nodeID, rest) ->
let (dag', noIncoming') = removeNode nodeID dag
in (nodeID:) <$> go dag' (S.union rest noIncoming')
Nothing ->
if null dag
then Just []
else Nothing
removeNode :: NodeID -> DAG a b -> (DAG a b, S.Set NodeID)
removeNode nodeID dag0 =
first doRemoveNode $ L.foldl' f (dag0, S.empty) (outgoingEdges nodeID dag0)
where
doRemoveNode dag = dag
{ nodeMap = M.delete nodeID (nodeMap dag) }
f (dag, nodeSet) edgeID =
let
nextID = endsWith edgeID dag
dag' = dag
{ edgeMap = M.delete edgeID (edgeMap dag)
, nodeMap =
let adj node =
node {ingoSet = S.delete edgeID (ingoSet node)}
in M.adjust adj nextID (nodeMap dag)
}
in
if null $ ingoingEdges nextID dag'
then (dag', S.insert nextID nodeSet)
else (dag', nodeSet)