module Data.Graph.Unordered
(
Graph
, DirGraph
, UndirGraph
, ValidGraph
, Edge (..)
, DirEdge (..)
, UndirEdge (..)
, EdgeType (..)
, NodeFrom (..)
, DirAdj (..)
, Identity (..)
, Context (..)
, AdjLookup
, Contextual (..)
, ValidContext
, FromContext (..)
, ToContext (..)
, isEmpty
, order
, hasNode
, ninfo
, nodes
, nodeDetails
, lnodes
, nlab
, neighbours
, size
, hasEdge
, einfo
, edges
, edgeDetails
, ledges
, elab
, edgePairs
, ledgePairs
, empty
, mkGraph
, buildGr
, insNode
, insEdge
, Mergeable
, merge
, mergeAs
, delNode
, delEdge
, delEdgeLabel
, delEdgesBetween
, Matchable
, match
, matchAs
, matchAny
, matchAnyAs
, nmap
, nmapFor
, emap
, emapFor
) where
import Data.Graph.Unordered.Internal
import Control.Arrow (first, second)
import Control.DeepSeq (NFData)
import Data.Function (on)
import Data.Functor.Identity
import Data.HashMap.Strict (HashMap)
import qualified Data.HashMap.Strict as HM
import Data.List (delete, foldl', groupBy, sortBy)
import Data.Maybe (listToMaybe)
import GHC.Generics (Generic)
type DirGraph = Graph DirEdge
type UndirGraph = Graph UndirEdge
type AdjLookup n el = HashMap Edge (n,el)
data DirEdge n = DE { fromNode :: !n
, toNode :: !n
}
deriving (Eq, Ord, Show, Read, Functor, Generic, NFData)
newtype UndirEdge n = UE { ueElem :: [n] }
deriving (Eq, Ord, Show, Read, Functor, Generic, NFData)
data DirAdj n = ToNode n
| FromNode n
deriving (Eq, Ord, Show, Read, Generic, NFData)
instance NodeFrom DirAdj where
getNode (ToNode n) = n
getNode (FromNode n) = n
instance EdgeType DirEdge where
type AdjType DirEdge = DirAdj
mkEdge = DE
otherN n (DE u v)
| n == u = ToNode v
| otherwise = FromNode u
toEdge u (ToNode v) = DE u v
toEdge v (FromNode u) = DE u v
edgeNodes (DE u v) = [u,v]
edgeTriple (DE u v, el) = (u,v,el)
instance EdgeType UndirEdge where
type AdjType UndirEdge = Identity
mkEdge u v = UE [u,v]
otherN n (UE vs) = Identity $ head (delete n vs)
toEdge u (Identity v) = UE [u,v]
edgeNodes = ueElem
edgeTriple (UE vs,el) = let [u,v] = vs
in (u,v,el)
data Context at n nl el = Ctxt { cNode :: !n
, cLabel :: !nl
, cAdj :: !(AdjLookup (at n) el)
}
deriving (Eq, Show, Read, Generic, NFData)
class Contextual ctxt where
type CNode ctxt :: *
type CAType ctxt :: * -> *
type CNLabel ctxt :: *
type CELabel ctxt :: *
type ValidContext et n nl el ctxt = (Contextual ctxt
,n ~ CNode ctxt
,AdjType et ~ CAType ctxt
,nl ~ CNLabel ctxt
,el ~ CELabel ctxt
)
instance Contextual (Context at n nl el) where
type CNode (Context at n nl el) = n
type CAType (Context at n nl el) = at
type CNLabel (Context at n nl el) = nl
type CELabel (Context at n nl el) = el
class (Contextual ctxt) => FromContext ctxt where
fromContext :: Context (CAType ctxt) (CNode ctxt) (CNLabel ctxt) (CELabel ctxt)
-> ctxt
class (Contextual ctxt) => ToContext ctxt where
toContext :: ctxt -> Context (CAType ctxt) (CNode ctxt) (CNLabel ctxt) (CELabel ctxt)
instance FromContext (Context at n nl el) where
fromContext = id
instance ToContext (Context at n nl el) where
toContext = id
instance Contextual (n, nl, AdjLookup (at n) el) where
type CNode (n, nl, AdjLookup (at n) el) = n
type CAType (n, nl, AdjLookup (at n) el) = at
type CNLabel (n, nl, AdjLookup (at n) el) = nl
type CELabel (n, nl, AdjLookup (at n) el) = el
instance FromContext (n, nl, AdjLookup (at n) el) where
fromContext (Ctxt n nl adj) = (n,nl,adj)
instance ToContext (n, nl, AdjLookup (at n) el) where
toContext (n,nl,adj) = Ctxt n nl adj
instance Contextual (n, nl, [(n,[el])]) where
type CNode (n, nl, [(n,[el])]) = n
type CAType (n, nl, [(n,[el])]) = AdjType UndirEdge
type CNLabel (n, nl, [(n,[el])]) = nl
type CELabel (n, nl, [(n,[el])]) = el
instance (Ord n) => FromContext (n, nl, [(n,[el])]) where
fromContext ctxt = (cNode ctxt
,cLabel ctxt
,toLookup (cAdj ctxt))
where
toLookup = map (\cels -> (fst (head cels), map snd cels))
. groupBy ((==) `on` fst)
. sortBy (compare `on` fst)
. map (first runIdentity)
. HM.elems
empty :: Graph et n nl el
empty = Gr HM.empty HM.empty minBound
isEmpty :: Graph et n nl el -> Bool
isEmpty = HM.null . nodeMap
order :: Graph et n nl el -> Int
order = HM.size . nodeMap
size :: Graph et n nl el -> Int
size = HM.size . edgeMap
buildGr :: (ValidGraph et n) => [Context (AdjType et) n nl el] -> Graph et n nl el
buildGr = foldr merge empty
ninfo :: (ValidGraph et n) => Graph et n nl el -> n -> Maybe ([Edge], nl)
ninfo g = fmap (first HM.keys) . (`HM.lookup` nodeMap g)
hasNode :: (ValidGraph et n) => Graph et n nl el -> n -> Bool
hasNode g n = HM.member n (nodeMap g)
nlab :: (ValidGraph et n) => Graph et n nl el -> n -> Maybe nl
nlab g = fmap snd . (`HM.lookup` nodeMap g)
neighbours :: (ValidGraph et n) => Graph et n nl el -> n -> [n]
neighbours g n = maybe [] (map (getNode . otherN n . fst . (edgeMap g HM.!)) . fst)
$ ninfo g n
hasEdge :: (ValidGraph et n) => Graph et n nl el -> Edge -> Bool
hasEdge g e = HM.member e (edgeMap g)
einfo :: (ValidGraph et n) => Graph et n nl el -> Edge -> Maybe (et n, el)
einfo g = (`HM.lookup` edgeMap g)
elab :: (ValidGraph et n) => Graph et n nl el -> Edge -> Maybe el
elab g = fmap snd . einfo g
nodes :: Graph et n nl el -> [n]
nodes = HM.keys . nodeMap
type Matchable et n nl el ctxt = (ValidGraph et n
,FromContext ctxt
,ValidContext et n nl el ctxt
)
match :: (ValidGraph et n) => n -> Graph et n nl el
-> Maybe (Context (AdjType et) n nl el, Graph et n nl el)
match n g = getCtxt <$> HM.lookup n nm
where
nm = nodeMap g
em = edgeMap g
getCtxt (adj,nl) = (ctxt, g')
where
ctxt = Ctxt n nl adjM
adjM = HM.map (first $ otherN n) (HM.intersection em adj)
g' = g { nodeMap = nm'
, edgeMap = em'
}
em' = em `HM.difference` adj
adjNs = filter (/=n)
. map (getNode . fst)
$ HM.elems adjM
nm' = foldl' (flip $ HM.adjust (first (`HM.difference`adj)))
(HM.delete n nm)
adjNs
matchAs :: (Matchable et n nl el ctxt) => n -> Graph et n nl el
-> Maybe (ctxt, Graph et n nl el)
matchAs n = fmap (first fromContext) . match n
matchAny :: (ValidGraph et n) => Graph et n nl el
-> Maybe (Context (AdjType et) n nl el, Graph et n nl el)
matchAny g
| isEmpty g = Nothing
| otherwise = flip match g . head . HM.keys $ nodeMap g
matchAnyAs :: (Matchable et n nl el ctxt) => Graph et n nl el
-> Maybe (ctxt, Graph et n nl el)
matchAnyAs = fmap (first fromContext) . matchAny
type Mergeable et n nl el ctxt = (ValidGraph et n
,ToContext ctxt
,ValidContext et n nl el ctxt
)
merge :: (ValidGraph et n) => Context (AdjType et) n nl el
-> Graph et n nl el -> Graph et n nl el
merge ctxt g = Gr nm' em' nextE'
where
n = cNode ctxt
adjM = cAdj ctxt
adj = HM.map (adjCount n . getNode . fst) adjM
nAdj = HM.toList
. foldl' (HM.unionWith HM.union) HM.empty
. map (uncurry toNAdj)
. HM.toList
$ adjM
toNAdj e (av,_) = let v = getNode av
in HM.singleton v (HM.singleton e (adjCount n v))
maxCE = fmap succ . listToMaybe . sortBy (flip compare) . HM.keys $ adjM
nextE = nextEdge g
nextE' = maybe nextE (max nextE) maxCE
em = edgeMap g
em' = em `HM.union` HM.map (first $ toEdge n) adjM
nm = nodeMap g
nm' = foldl' (\m (v,es) -> HM.adjust (first (`HM.union`es)) v m)
(HM.insert n (adj,cLabel ctxt) nm)
nAdj
mergeAs :: (Mergeable et n nl el ctxt) => ctxt -> Graph et n nl el
-> Graph et n nl el
mergeAs = merge . toContext
insNode :: (ValidGraph et n) => n -> nl -> Graph et n nl el -> Graph et n nl el
insNode n l g = g { nodeMap = HM.insert n (HM.empty, l) (nodeMap g) }
insEdge :: (ValidGraph et n) => (n,n,el) -> Graph et n nl el
-> (Edge, Graph et n nl el)
insEdge (u,v,l) g = (e, Gr nm' em' (succ e))
where
e = nextEdge g
nm' = addE u . addE v $ nodeMap g
addE = HM.adjust (first $ HM.insert e (adjCount u v))
em' = HM.insert e (mkEdge u v, l) (edgeMap g)
delNode :: (ValidGraph et n) => n -> Graph et n nl el -> Graph et n nl el
delNode n g = maybe g snd $ match n g
delEdge :: (ValidGraph et n) => Edge -> Graph et n nl el -> Graph et n nl el
delEdge e g = g { nodeMap = foldl' (flip delE) (nodeMap g) ens
, edgeMap = HM.delete e (edgeMap g)
}
where
ens = maybe [] (edgeNodes . fst) (HM.lookup e (edgeMap g))
delE = HM.adjust (first $ HM.delete e)
delEdgeLabel :: (ValidGraph et n, Eq el) => (n,n,el) -> Graph et n nl el
-> Graph et n nl el
delEdgeLabel (u,v,l) g
| HM.null es = g
| otherwise = g { nodeMap = delEs u . delEs v $ nm
, edgeMap = em `HM.difference` es
}
where
nm = nodeMap g
em = edgeMap g
es = maybe HM.empty (HM.filter isE . HM.intersection em . fst) $ HM.lookup u nm
isE (et,el) = getNode (otherN u et) == v && el == l
delEs = HM.adjust (first (`HM.difference`es))
delEdgesBetween :: (ValidGraph et n) => n -> n -> Graph et n nl el
-> Graph et n nl el
delEdgesBetween u v g
| HM.null es = g
| otherwise = g { nodeMap = delEs u . delEs v $ nm
, edgeMap = em `HM.difference` es
}
where
nm = nodeMap g
em = edgeMap g
es = maybe HM.empty (HM.filter isE . HM.intersection em . fst) $ HM.lookup u nm
isE (et,_) = getNode (otherN u et) == v
delEs = HM.adjust (first (`HM.difference`es))
nmap :: (ValidGraph et n) => (nl -> nl') -> Graph et n nl el -> Graph et n nl' el
nmap f = withNodeMap (HM.map (second f))
nmapFor :: (ValidGraph et n) => (nl -> nl) -> Graph et n nl el -> n
-> Graph et n nl el
nmapFor f g n = withNodeMap (HM.adjust (second f) n) g
emap :: (ValidGraph et n) => (el -> el') -> Graph et n nl el -> Graph et n nl el'
emap f = withEdgeMap (HM.map (second f))
emapFor :: (ValidGraph et n) => (el -> el) -> Graph et n nl el -> Edge
-> Graph et n nl el
emapFor f g e = withEdgeMap (HM.adjust (second f) e) g