UNPKG

@haragei/dag

Version:

A Directed Acyclic Graph (DAG) library with online cycle detection and topological ordering.

326 lines (323 loc) 11.6 kB
/** * 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 };