mirror of
https://code.naskya.net/repos/ndqEd
synced 2025-03-20 15:14:54 +09:00
207 lines
6.9 KiB
Haskell
207 lines
6.9 KiB
Haskell
{- This file is part of Vervis.
|
|
-
|
|
- Written in 2016 by fr33domlover <fr33domlover@riseup.net>.
|
|
-
|
|
- ♡ Copying is an act of love. Please copy, reuse and share.
|
|
-
|
|
- The author(s) have dedicated all copyright and related and neighboring
|
|
- rights to this software to the public domain worldwide. This software is
|
|
- distributed without any warranty.
|
|
-
|
|
- You should have received a copy of the CC0 Public Domain Dedication along
|
|
- with this software. If not, see
|
|
- <http://creativecommons.org/publicdomain/zero/1.0/>.
|
|
-}
|
|
|
|
-- | Layering of directed acyclic graphs
|
|
module Data.Graph.Inductive.Query.Layer
|
|
( -- * Intro
|
|
-- $into
|
|
|
|
-- * Forward Layer
|
|
-- $forward
|
|
layer
|
|
, layern
|
|
, layerWith
|
|
, layernWith
|
|
|
|
-- * Backward Layer
|
|
-- $backward
|
|
, rlayer
|
|
, rlayern
|
|
, rlayerWith
|
|
, rlayernWith
|
|
|
|
-- * Custom Layer
|
|
-- $custom
|
|
, xlayern
|
|
, xlayernWith
|
|
)
|
|
where
|
|
|
|
import Prelude
|
|
|
|
import Data.Graph.Inductive.Basic (gsel)
|
|
import Data.Graph.Inductive.Graph
|
|
import Data.Graph.Inductive.Internal.Queue
|
|
import Data.List (sortOn)
|
|
|
|
import qualified Data.HashMap.Lazy as M
|
|
|
|
import qualified Data.HashMap.Lazy.Local as ML
|
|
|
|
noIn :: Graph g => g a b -> [Node]
|
|
noIn = map node' . gsel (null . pre')
|
|
|
|
noOut :: Graph g => g a b -> [Node]
|
|
noOut = map node' . gsel (null . suc')
|
|
|
|
-- $intro
|
|
-- Layering a directed acyclic graph basically means to partition its nodes
|
|
-- such that all the edges point in the same direction. Layering is often used
|
|
-- for graph visualization, an therefore requires that the result has certain
|
|
-- human-friendly properties.
|
|
--
|
|
-- This module currently offers a very simple algorithm meant for DAGs that are
|
|
-- transitively reduced, i.e. if edges AB and BC exist, an edge AC shouldn't
|
|
-- exist in the graph. In other words, assuming the edges represent partial
|
|
-- ordering of the nodes, no edge should be possible to deduce from other
|
|
-- edges.
|
|
|
|
-- $forward
|
|
-- Forward layering starts from a set of nodes, usually the nodes which don't
|
|
-- have in-edges, and builds the layers by traversing the out-edges
|
|
-- recursively. The initial nodes are the first layer, their children are the
|
|
-- second layer, the children's children are the third layer, and so on.
|
|
|
|
-- | The initial nodes are the nodes which don't have in-edges.
|
|
layer :: Graph g => g a b -> [[Node]]
|
|
layer = layerWith node'
|
|
|
|
-- | Specify the initial nodes.
|
|
layern :: Graph g => [Node] -> g a b -> [[Node]]
|
|
layern = layernWith node'
|
|
|
|
-- | Specify function to apply to nodes whose result will be in the result
|
|
-- list. The initial nodes are the nodes which don't have in-edges.
|
|
layerWith :: Graph g => (Context a b -> c) -> g a b -> [[c]]
|
|
layerWith result graph = layernWith result (noIn graph) graph
|
|
|
|
-- | Specify function to apply to nodes whose result will be in the result
|
|
-- list, and specify initial nodes.
|
|
layernWith :: Graph g => (Context a b -> c) -> [Node] -> g a b -> [[c]]
|
|
layernWith = xlayernWith suc' (not . null . pre')
|
|
|
|
-- $backward
|
|
-- Backward layering starts from a set of nodes, usually the nodes which don't
|
|
-- have out-edges, and builds the layers by traversing the in-edges
|
|
-- recursively. The initial nodes are the first layer, their parents are the
|
|
-- second layer, the parents' parents are the third layer, and so on.
|
|
|
|
-- | The initial nodes are the nodes which don't have out-edges.
|
|
rlayer :: Graph g => g a b -> [[Node]]
|
|
rlayer = rlayerWith node'
|
|
|
|
-- | Specify the initial nodes.
|
|
rlayern :: Graph g => [Node] -> g a b -> [[Node]]
|
|
rlayern = rlayernWith node'
|
|
|
|
-- | Specify function to apply to nodes whose result will be in the result
|
|
-- list. The initial nodes are the nodes which don't have out-edges.
|
|
rlayerWith :: Graph g => (Context a b -> c) -> g a b -> [[c]]
|
|
rlayerWith result graph = rlayernWith result (noOut graph) graph
|
|
|
|
-- | Specify function to apply to nodes whose result will be in the result
|
|
-- list, and specify initial nodes.
|
|
rlayernWith :: Graph g => (Context a b -> c) -> [Node] -> g a b -> [[c]]
|
|
rlayernWith = xlayernWith pre' (not . null . suc')
|
|
|
|
-- $custom
|
|
-- Custom layering starts from a set of nodes, and builds the layers by
|
|
-- traversing edges recursively. A user-specified function determines which
|
|
-- edges are traversed, and another functions is used for checking whether
|
|
-- there are edges through which a given node can be reached. For example, if
|
|
-- you follow just out-edges that point from red-colored nodes, the second
|
|
-- function would check whether the given nodes has red-colored nodes pointing
|
|
-- to it. The initial nodes are the first layer, the nodes reached from them
|
|
-- are the second layer and so on.
|
|
|
|
-- | Specify which paths to follow, and the initial nodes.
|
|
xlayern
|
|
:: Graph g
|
|
=> (Context a b -> [Node])
|
|
-> (Context a b -> Bool)
|
|
-> [Node]
|
|
-> g a b
|
|
-> [[Node]]
|
|
xlayern follow back = xlayernWith follow back node'
|
|
|
|
-- (1) All nodes have unspecified layer
|
|
-- (2) Mark all child-less nodes with layer 1 and place in a queue
|
|
-- (3) Dequeue a node N and remove N from the graph
|
|
-- (4) For each parent of N, P:
|
|
-- (5) layer(P) = max (layer(P), layer(N)+1)
|
|
-- (6) If N was P's only child, enqueue P
|
|
-- (7) Jump back to 3
|
|
depths
|
|
:: Graph g
|
|
=> (Context a b -> [Node])
|
|
-> (Context a b -> Bool)
|
|
-> g a b
|
|
-> Queue Node
|
|
-> M.HashMap Node Int
|
|
-> M.HashMap Node Int
|
|
depths follow back = go
|
|
where
|
|
depth n m =
|
|
case M.lookup n m of
|
|
Nothing -> error "Layer of node not found, should never happen"
|
|
Just d -> d
|
|
visit g l p (m, q) =
|
|
( case M.lookup p m of
|
|
Nothing -> M.insert p l m
|
|
Just d ->
|
|
if l > d
|
|
then M.insert p l m
|
|
else m
|
|
, if back $ context g p
|
|
then q
|
|
else queuePut p q
|
|
)
|
|
go g q m =
|
|
if queueEmpty q
|
|
then m
|
|
else
|
|
let (n, q') = queueGet q
|
|
in case match n g of
|
|
(Nothing, g') -> go g' q' m
|
|
(Just c, g') ->
|
|
let ps = follow c
|
|
l = depth n m + 1
|
|
(m', q'') = foldr (visit g' l) (m, q') ps
|
|
in go g' q'' m'
|
|
|
|
-- | Specify which paths to follow, a function to apply to nodes whose result
|
|
-- will be in the result list, and the initial nodes.
|
|
xlayernWith
|
|
:: Graph g
|
|
=> (Context a b -> [Node])
|
|
-> (Context a b -> Bool)
|
|
-> (Context a b -> c)
|
|
-> [Node]
|
|
-> g a b
|
|
-> [[c]]
|
|
xlayernWith follow back result initials graph =
|
|
-- Sort by layer number and drop the layer numbers, leaving just nodes
|
|
map snd $ sortOn fst $ M.toList $
|
|
-- Map nodes to results according to user specified function
|
|
M.map (map $ result . context graph) $
|
|
-- Turn node-to-layer map into layer-to-nodes map
|
|
ML.flip $
|
|
-- Determine the layer number for each node
|
|
depths
|
|
follow
|
|
back
|
|
graph
|
|
(queuePutList initials mkQueue)
|
|
(M.fromList $ zip initials (repeat 1))
|