@haragei/dag
Version:
A Directed Acyclic Graph (DAG) library with online cycle detection and topological ordering.
326 lines (323 loc) • 11.6 kB
text/typescript
/**
* Represents a set of nodes that can be processed in parallel as defined by
* their relationship to each other in the graph.
*/
type ParallelCollection<T> = Set<T | SerialCollection<T>>;
/**
* Represents a list of nodes that must be processed sequentially as defined by
* their relationship to each other in the graph.
*/
type SerialCollection<T> = (T | Set<T>)[];
/**
* A Directed Acyclic Graph structure with online cycle detection and topological ordering.
*/
declare class DAG<T> {
#private;
/**
* Creates a new DAG with optional initial nodes.
*
* @param initialNodes - An optional iterable of initial nodes to populate the graph with.
*/
constructor(initialNodes?: Iterable<T>);
/**
* 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.
*
* @return An array of all graph nodes in a topological order.
*/
get order(): T[];
/**
* Returns the number of nodes in the graph.
*
* @return Number of nodes in the graph
*/
get size(): number;
/**
* Iterates over all nodes in the graph in a topological order.
*
* @alias keys
* @return An iterator over all nodes in the graph.
*/
[Symbol.iterator](): IterableIterator<T>;
/**
* Iterates over all nodes in the graph in a topological order.
*
* @return An iterator over all nodes in the graph.
*/
keys(): IterableIterator<T>;
/**
* Creates a new, independent copy of this DAG.
*
* @return Clone of this DAG.
*/
copy(): DAG<T>;
/**
* Removes all nodes (and their edges) from the graph.
*
* @return This DAG.
*/
clear(): this;
/**
* Adds a node to the graph.
* If the node already exists, this is a no-op.
*
* @param node - The node to add.
* @return This DAG.
*/
add(node: T): this;
/**
* Checks if a specific node exists in the graph.
*
* @param node - The node to check.
* @return `true` if the node exists, `false` otherwise.
*/
has(node: T): boolean;
/**
* 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.
*
* @param node - The node to remove.
* @return This DAG.
*/
delete(node: T): this;
/**
* 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.
*
* @param from - The "source" node.
* @param to - The "target" node.
* @return This DAG.
*/
addEdge(from: T, to: T): this;
/**
* 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.
*
* @param from - The "source" node.
* @param to - The "target" node.
* @return `true` if the edge was added, `false` otherwise.
*/
tryAddEdge(from: T, to: T): boolean;
/**
* Checks if a specific (directed) edge exists in the graph.
*
* @param from - The "source" node.
* @param to - The "target" node.
* @return `true` if the edge exists, `false` otherwise.
*/
hasEdge(from: T, to: T): boolean;
/**
* 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.
*
* @param from - The "source" node.
* @param to - The "target" node.
* @return This DAG.
*/
deleteEdge(from: T, to: T): this;
/**
* Removes all outgoing edges from a given node.
* If such node doesn't exist, this is a no-op.
*
* @param node - The node to remove outgoing edges from.
* @return This DAG.
*/
deleteOutgoingEdgesOf(node: T): this;
/**
* Removes all incoming edges to a given node.
* If such node doesn't exist, this is a no-op.
*
* @param node - The node to remove incoming edges to.
* @return This DAG.
*/
deleteIncomingEdgesOf(node: T): this;
/**
* 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.
*
* @param nodes - The nodes to sort.
* @return An array of the given nodes in a topological order.
*/
getNodeOrder(...nodes: T[]): T[];
/**
* 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.
*
* @param nodes - The nodes to sort.
* @return The input array of nodes, sorted in a topological order.
*/
sortNodes(nodes: T[]): T[];
/**
* Returns (an unordered) set of all immediate predecessors of the given nodes.
*
* @param nodes - The nodes to get immediate predecessors of.
* @return An unordered set of all immediate predecessors of the given nodes.
*/
getImmediatePredecessorsOf(...nodes: T[]): Set<T>;
/**
* Returns an iterable of all immediate predecessors of the given nodes which
* iterates over them in a topological order.
*
* @param nodes - The nodes to get immediate predecessors of.
* @return A topologically ordered iterable of all immediate predecessors of the given nodes.
*/
getOrderedImmediatePredecessorsOf(...nodes: T[]): Iterable<T>;
/**
* Returns (an unordered) set of all predecessors of the given nodes.
*
* @param nodes - The nodes to get predecessors of.
* @return An unordered set of all predecessors of the given nodes.
*/
getPredecessorsOf(...nodes: T[]): Set<T>;
/**
* Returns an iterable of all predecessors of the given nodes which
* iterates over them in a topological order.
*
* @param nodes - The nodes to get predecessors of.
* @return A topologically ordered iterable of all predecessors of the given nodes.
*/
getOrderedPredecessorsOf(...nodes: T[]): Iterable<T>;
/**
* Returns (an unordered) set of all immediate successors of the given nodes.
*
* @param nodes - The nodes to get immediate successors of.
* @return An unordered set of all immediate successors of the given nodes.
*/
getImmediateSuccessorsOf(...nodes: T[]): Set<T>;
/**
* Returns an iterable of all immediate successors of the given nodes which
* iterates over them in a topological order.
*
* @param nodes - The nodes to get immediate successors of.
* @return A topologically ordered iterable of all immediate successors of the given nodes.
*/
getOrderedImmediateSuccessorsOf(...nodes: T[]): Iterable<T>;
/**
* Returns (an unordered) set of all successors of the given nodes.
*
* @param nodes - The nodes to get successors of.
* @return An unordered set of all successors of the given nodes.
*/
getSuccessorsOf(...nodes: T[]): Set<T>;
/**
* Returns an iterable of all successors of the given nodes which
* iterates over them in a topological order.
*
* @param nodes - The nodes to get successors of.
* @return A topologically ordered iterable of all successors of the given nodes.
*/
getOrderedSuccessorsOf(...nodes: T[]): Iterable<T>;
/**
* 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`.
*
*
* @param from - The "source" node.
* @param to - The "target" node.
* @return `true` if a directed path exists, `false` otherwise.
*/
hasPath(from: T, to: T): boolean;
/**
* 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 of A, and
* - all immediate successors of B be immediate successors of A.
*
* 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.
*
* @param a - The first node to merge.
* @param b - The second node to merge.
* @return This DAG.
*/
mergeNodes(a: T, b: T): this;
/**
* Tries to merge 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 of A, and
* - all immediate successors of B be immediate successors of A.
*
* 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.
*
* @param a - The first node to merge.
* @param b - The second node to merge.
* @return True if the nodes were successfully merged, or if
* at least one of the given nodes is not present in the graph.
*/
tryMergeNodes(a: T, b: T): boolean;
/**
* Returns an array of node arrays, where each inner array represents a subgraph.
*
* The nodes in each subgraph are topologically ordered.
*
* @return An array of independent subgraphs.
*/
subGraphs(): T[][];
/**
* 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
* ```typescript
* 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' }
*```
*
* @return A set of all subgraphs in the graph, parallelized.
*/
parallelize(): ParallelCollection<T>;
}
/**
* Error thrown when a cycle would be introduced in a DAG.
*/
declare class CycleError extends Error {
constructor();
}
export { CycleError, DAG, type ParallelCollection, type SerialCollection };