A TypeScript library implementing Directed Acyclic Graph (DAG) data structure with online cycle detection and topological ordering.
A DAG is a finite graph that consists of nodes (a.k.a. vertices) and edges, with the edges directed from one node to another, ensuring that there are no cycles. This means you cannot start at any node and follow a consistently directed sequence of edges that eventually loops back to that node.
-
Online Cycle Detection: The library dynamically checks for cycles as edges are added, ensuring the graph remains acyclic.
-
Topological Ordering: It provides O(1) getter to order the nodes of the graph linearly such that for every directed edge from node
u
to nodev
,u
comes beforev
in the ordering. This is particularly useful in scenarios like task scheduling, where certain tasks must precede others.
npm install @haragei/dag
pnpm
pnpm add @haragei/dag
yarn
yarn add @haragei/dag
Creating a Graph:
import { DAG } from '@haragei/dag';
// Populate the graph with initial nodes
const graph = new DAG(['A', 'B', 'C']);
Adding nodes:
// Add few more nodes
graph
.add('D')
.add('E')
.add('F');
Adding edges and detecting cycles:
// Add edge from 'A' to 'B'
graph.addEdge('A', 'B'); // No cycle
// Add edge from 'B' to 'C'
graph.addEdge('B', 'C'); // No cycle
// Attempt to add edge from 'C' to 'A'
try {
graph.addEdge('C', 'A'); // Cycle detected
} catch (error) {
console.error(error.message); // "Cycle detected"
}
// Verify no edge with cycle was added:
console.log(graph.hasEdge('C', 'A')); // false
// Add few more valid edges
graph.addEdge('B', 'D');
graph.addEdge('D', 'E');
graph.addEdge('E', 'C');
graph.addEdge('C', 'F');
Topological order:
console.log(graph.order); // ['A', 'B', 'D', 'E', 'C', 'F']
A Directed Acyclic Graph structure with online cycle detection and topological ordering.
Create / Copy:
Properties:
Iteration:
Nodes:
Edges:
.addEdge()
.tryAddEdge()
.hasEdge()
.hasPath()
.deleteEdge()
.deleteOutgoingEdgesOf()
.deleteIncomingEdgesOf()
Sorting / Ordering:
Subgraphs:
Predecessors / Successors:
.getImmediatePredecessorsOf()
.getOrderedImmediatePredecessorsOf()
.getPredecessorsOf()
.getOrderedPredecessorsOf()
.getImmediateSuccessorsOf()
.getOrderedImmediateSuccessorsOf()
.getSuccessorsOf()
.getOrderedSuccessorsOf()
Merging:
Creates a new DAG with optional initial nodes.
Parameters:
initialNodes
: An optional iterable of nodes to populate the graph with.
Returns: a new instance of DAG
.
Creates a new, independent copy of this DAG.
Returns: Clone of this DAG
.
Returns a list of all nodes in the graph in a topological order.
Note: for every U -> V
directed edge, U
will appear before V
in a topological order.
Returns: An array of all graph nodes in a topological order.
Returns the number of nodes in the graph.
Returns: Number of nodes in the graph.
Returns an iterator that yields all nodes in the graph in a topological order.
Alias: keys()
Returns: An iterator over all graph nodes in the graph.
Returns an iterator that yields all nodes in the graph in a topological order.
Returns: An iterator over all graph nodes in the graph.
Removes all nodes (and their edges) from the graph.
Returns: This DAG
.
Adds a new node to the graph.
If the node already exists, this is a no-op.
Parameters:
node
: The node to add.
Returns: This DAG
.
Checks if a specific node exists in the graph.
Parameters:
node
: The node to check.
Returns: true
if the node exists, false
otherwise.
Removes a specified node from the graph.
If such node doesn't exist, this is a no-op.
Note: This also removes all edges from or to the specified node.
Parameters:
node
: The node to remove.
Returns: This DAG
.
Tries to add a directed edge to the graph.
If any of the nodes doesn't already exist, it will be added.
If inserting the given edge would introduce a cycle no changes are made to the graph and CycleError
is thrown.
Adding an edge from a node to that same node (i.e. from
and to
are the same) is considered a cycle and such edge cannot be added.
Parameters:
from
: The "source" node.to
: The "target" node.
Returns: This DAG
.
Tries to add a directed edge to the graph.
If any of the nodes doesn't already exist, it will be added.
If inserting the given edge would introduce a cycle no changes are made to the graph and false
is returned.
Adding an edge from a node to that same node (i.e. from
and to
are the same) is considered a cycle and such edge cannot be added.
Parameters:
from
: The "source" node.to
: The "target" node.
Returns: true
if the edge was added, false
otherwise.
Checks if a specific (directed) edge exists in the graph.
Parameters:
from
: The "source" node.to
: The "target" node.
Returns: true
if the edge exists, false
otherwise.
Checks whether a directed path between two nodes exists in the graph.
If A is directly connected to B, hasPath(A, B)
is exactly the same as hasEdge(A, B)
.
On the other hand, if only the edges A -> B
and A -> C
exists in the graph, a hasPath(A, C)
returns true
, while hasEdge(A, C)
returns false
.
Parameters:
from
: The "source" node.to
: The "target" node.
Returns: true
if a directed path exists, false
otherwise.
Removes a specified edge from the graph.
If such edge doesn't exist, this is a no-op.
Note: this removes a directed edge. If an edge A -> B
exists, it will not be removed with a removeEdge(B, A)
call.
Parameters:
from
: The "source" node.to
: The "target" node.
Returns: This DAG
.
Removes all outgoing edges from a given node.
If such node doesn't exist, this is a no-op.
Parameters:
node
: The node to remove outgoing edges from.
Returns: This DAG
.
Removes all incoming edges to a given node.
If such node doesn't exist, this is a no-op.
Parameters:
node
: The node to remove incoming edges to.
Returns: This DAG
.
Returns the given nodes in a topological order.
In a case that a node does not exist in the graph, it is pushed to the end of the array.
Parameters:
nodes
: The nodes to sort.
Returns: An array of the given nodes in a topological order.
Sorts the given array of nodes, in place by their topological order.
In a case that a node does not exist in the graph, it is pushed to the end of the array.
Parameters:
nodes
: The nodes to sort.
Returns: The input array of nodes, sorted in a topological order.
Returns an array of node arrays, where each inner array represents a subgraph.
The nodes in each subgraph are topologically ordered.
Returns: An array of independent subgraphs.
Returns a set of all subgraphs in the graph.
Each subgraph is represented either as a single node, or as an array which is meant to be processed sequentially and its items are either a single node or a Set of nodes that can be processed in parallel.
Example:
const dag = new DAG<string>(['Z']);
dag.addEdge('A', 'B');
dag.addEdge('A', 'C');
dag.addEdge('B', 'D');
dag.addEdge('C', 'D');
dag.addEdge('X', 'Y');
console.log(dag.parallelize());
// Set(3) { [ 'A', Set(2) { 'B', 'C' }, 'D' ], [ 'X', 'Y' ], 'Z' }
Returns: A set of all subgraphs in the graph, parallelized.
Returns (an unordered) set of all immediate predecessors of the given nodes.
Parameters:
nodes
: The nodes to get immediate predecessors of.
Returns: An unordered set of all immediate predecessors of the given nodes.
Returns an iterable of all immediate predecessors of the given nodes which iterates over them in a topological order.
Parameters:
nodes
: The nodes to get immediate predecessors of.
Returns: A topologically ordered iterable of all immediate predecessors of the given nodes.
Returns (an unordered) set of all predecessors of the given nodes.
Parameters:
nodes
: The nodes to get predecessors of.
Returns: An unordered set of all predecessors of the given nodes.
Returns an iterable of all predecessors of the given nodes which iterates over them in a topological order.
Parameters:
nodes
: The nodes to get predecessors of.
Returns: A topologically ordered iterable of all predecessors of the given nodes.
Returns (an unordered) set of all immediate successors of the given nodes.
Parameters:
nodes
: The nodes to get immediate successors of.
Returns: An unordered set of all immediate successors of the given nodes.
Returns an iterable of all immediate successors of the given nodes which iterates over them in a topological order.
Parameters:
nodes
: The nodes to get immediate successors of.
Returns: A topologically ordered iterable of all immediate successors of the given nodes.
Returns (an unordered) set of all successors of the given nodes.
Parameters:
nodes
: The nodes to get successors of.
Returns: An unordered set of all successors of the given nodes.
Returns an iterable of all successors of the given nodes which iterates over them in a topological order.
Parameters:
nodes
: The nodes to get successors of.
Returns: A topologically ordered iterable of all successors of the given nodes.
Merges two nodes if such action would not introduce a cycle.
"Merging" of A
and B
is performed by making:
- all immediate predecessors of
B
be immediate predecessors ofA
, and - all immediate successors of
B
be immediate successors ofA
.
After that remapping, node B
gets removed from the graph.
Note: This method is a no-op if either A
or B
is absent in the graph.
If there is a path between A
and B
(in either direction), a CycleError
gets thrown, and the graph is not changed.
Parameters:
a
: The first node to merge.b
: The second node to merge.
Returns: This DAG
.
Merges two nodes if such action would not introduce a cycle.
"Merging" of A
and B
is performed by making:
- all immediate predecessors of
B
be immediate predecessors ofA
, and - all immediate successors of
B
be immediate successors ofA
.
After that remapping, node B
gets removed from the graph.
Note: This method is a no-op if either A
or B
is absent in the graph.
If there is a path between A
and B
(in either direction), the merging would introduce a cycle so this function returns false
, and the graph is not changed.
Parameters:
a
: The first node to merge.b
: The second node to merge.
Returns: This DAG
.
The core algorithm for cycle detection & dynamic topological ordering is an implementation of the following paper:
PEARCE, David J.; KELLY, Paul HJ.
A dynamic algorithm for topologically sorting directed acyclic graphs.-- Lecture notes in computer science, 2004, 3059: 383-398. [PDF]
Contributions are welcome! For feature requests and bug reports, please submit an issue.