graph-data-structure
Version:
A graph data structure with topological sort.
268 lines (247 loc) • 9.73 kB
TypeScript
declare class Graph<Node = string, LinkProps = never> {
/**
* Contains all the nodes added to the graph.
*/
nodes: Set<Node>;
/**
* The adjacency list of the graph.
*/
edges: Map<Node, Set<Node>>;
/**
* The weights of edges.
*
* Map<SourceNode, Map<TargetNode, EdgeWeight>>
*/
edgeWeights: Map<Node, Map<Node, EdgeWeight>>;
/**
* Arbitrary properties of edges.
* Map<SourceNode, Map<TargetNode, EdgeProperties>>
*/
edgeProperties: Map<Node, Map<Node, LinkProps>>;
/**
* Adds a node to the graph.
* If node was already added, this function does nothing.
* If node was not already added, this function sets up an empty adjacency list.
*/
addNode(node: Node): this;
/**
* Removes a node from the graph.
* Also removes incoming and outgoing edges.
*/
removeNode(node: Node): this;
/**
* Gets the adjacent nodes set for the given node.
*/
adjacent(node: Node): Set<Node> | undefined;
/**
* Sets the weight of the given edge.
*/
setEdgeWeight(source: Node, target: Node, weight: EdgeWeight): this;
/**
* Gets the weight of the given edge or `1` if not set.
*/
getEdgeWeight(source: Node, target: Node): number;
/**
* Set the properties of the given edge.
*/
setEdgeProperties(source: Node, target: Node, props: LinkProps): this;
/**
* Get the properties of the given edge or undefined if the edge doesn't exist .
*/
getEdgeProperties(source: Node, target: Node): LinkProps | undefined;
/**
* Adds an edge from the `source` node to `target` node.
* This method will create the nodes if they were not already added.
*/
addEdge(source: Node, target: Node, ...args: AddEdgeArgs<LinkProps>): this;
/**
* Removes the edge from the `source` node to `target` node.
* Does not remove the nodes themselves.
* Does nothing if the edge does not exist.
*/
removeEdge(source: Node, target: Node): this;
/**
* Returns true if there is an edge from the `source` node to `target` node..
*/
hasEdge(source: Node, target: Node): boolean;
}
type AddEdgeArgs<LinkProps> = [LinkProps] extends [never] ? [weight?: EdgeWeight] | [opts?: {
weight?: EdgeWeight;
}] : [opts: {
weight?: EdgeWeight;
props: LinkProps;
}];
type EdgeWeight = number;
type Edge<NodeIdentity = unknown, Props = unknown> = {
source: NodeIdentity;
target: NodeIdentity;
weight?: EdgeWeight;
props: Props;
};
type Serialized<Node = unknown, LinkProps = unknown, NodeIdentity = Node> = {
nodes: Node[];
links: Edge<NodeIdentity, LinkProps>[];
};
type SerializedInput<Node = unknown, LinkProps = unknown> = {
nodes: ReadonlyArray<Node>;
links: ReadonlyArray<Edge<Node, LinkProps>>;
};
type NoInfer<T> = [T][T extends any ? 0 : never];
type NextWeightFnParams<Node = unknown, LinkProps = unknown> = {
edgeWeight: EdgeWeight;
currentPathWeight: EdgeWeight | undefined;
hop: number;
graph: Graph<Node, LinkProps>;
path: readonly [NoInfer<Node>, NoInfer<Node>, ...NoInfer<Node>[]];
previousNode: NoInfer<Node> | undefined;
currentNode: NoInfer<Node>;
props: LinkProps;
};
declare class CycleError extends Error {
constructor(message: string);
}
type DepthFirstSearchOptions<Node, LinkProps> = {
/**
* Use those nodes as source nodes.
* @default all the nodes in the graph are used as source nodes.
*/
sourceNodes?: Node[];
/**
* Include or exclude the source nodes from the result.
* @default true
*/
includeSourceNodes?: boolean;
/**
* Throw an error when a cycle is detected.
* @default false
*/
errorOnCycle?: boolean;
/**
* A function that is executed to determine if the branch should be visited or not.
* @param source the current node
* @param target the next node to explore
* @param graph the graph instance being explored
* @returns boolean
*/
shouldFollow?: (args: {
source: Node;
target: Node;
props: LinkProps;
graph: Graph<Node, LinkProps>;
}) => boolean;
};
/**
* Depth First Search algorithm, inspired by
* Cormen et al. "Introduction to Algorithms" 3rd Ed. p. 604
*/
declare function depthFirstSearch<Node, LinkProps>(graph: Graph<Node, LinkProps>, opts?: DepthFirstSearchOptions<NoInfer<Node>, NoInfer<LinkProps>>): Node[];
/**
* Dijkstra's Shortest Path Algorithm.
* Cormen et al. "Introduction to Algorithms" 3rd Ed. p. 658
* Variable and function names correspond to names in the book.
*/
declare function shortestPath<Node, LinkProps>(graph: Graph<Node, LinkProps>, source: NoInfer<Node>, destination: NoInfer<Node>, nextWeightFn?: (params: NextWeightFnParams) => number): {
nodes: [Node, Node, ...Node[]];
weight: number | undefined;
};
declare function shortestPaths<Node, LinkProps>(graph: Graph<Node, LinkProps>, source: NoInfer<Node>, destination: NoInfer<Node>): {
nodes: [Node, Node, ...Node[]];
weight: number | undefined;
}[];
/**
* The topological sort algorithm yields a list of visited nodes
* such that for each visited edge (u, v), u comes before v in the list.
* Amazingly, this comes from just reversing the result from depth first search.
* Cormen et al. "Introduction to Algorithms" 3rd Ed. p. 613
*/
type TopologicalSortOptions<Node, LinkProps> = {
/**
* Run the first on those nodes.
* @default all the nodes of the graph.
*/
sourceNodes?: Node[];
/**
* Include the source nodes from the result.
* @default true
*/
includeSourceNodes?: boolean;
/**
* A function that is executed to determine if the branch should be visited or not.
* @param source the current node
* @param target the next node to explore
* @param graph the graph instance being explored
* @returns boolean
*/
shouldFollow?: (args: {
source: Node;
target: Node;
props: LinkProps;
graph: Graph<Node, LinkProps>;
}) => boolean;
};
declare function topologicalSort<Node, LinkProps>(graph: Graph<Node, LinkProps>, opts?: TopologicalSortOptions<NoInfer<Node>, NoInfer<LinkProps>>): Node[];
/**
* Return an array containing the lowest common ancestors.
*
* Inspired by https://github.com/relaxedws/lca/blob/master/src/LowestCommonAncestor.php code
* but uses depth search instead of breadth. Also uses some optimizations.
*/
declare function lowestCommonAncestors<Node, LinkProps>(graph: Graph<Node, LinkProps>, node1: NoInfer<Node>, node2: NoInfer<Node>): Node[];
/**
* Computes the indegree for the given node.
* Not very efficient, costs O(E) where E = number of edges.
*/
declare function indegree<Node>(graph: Graph<Node>, node: NoInfer<Node>): number;
declare function outdegree<Node>(graph: Graph<Node>, node: NoInfer<Node>): number;
/**
* Clone the graph data structures.
* Nodes references are preserves.
*/
declare function cloneGraph<Node, LinkProps>(graph: Graph<Node, LinkProps>): Graph<Node, LinkProps>;
/**
* Perform a depth first search to detect an eventual cycle.
*
* You can provide a `shouldFollow` function to constrain the traversing and
* provide `sourceNodes` to explore a particular sub-graphs.
*/
declare function hasCycle<Node, LinkProps>(graph: Graph<Node, LinkProps>, opts?: Pick<DepthFirstSearchOptions<Node, LinkProps>, 'shouldFollow' | 'sourceNodes'>): boolean;
/**
* Serializes the graph.
*/
type SerializeGraphOptions<IncludeDefaultWeight extends boolean = false> = {
/**
* If no weight is set on an edge, the default weight is `1`.
* When `includeDefaultWeight: true`, the serialization will include the edge's weight even when none is set or
* the value has been set to `1`.
*
* @default false
*/
includeDefaultWeight?: IncludeDefaultWeight;
};
/**
* Serialize the graph data set : nodes, edges, edges weight & properties.
*
* Optionally, you can pass a function that returns a unique value for a given node.
* When provided, the function will be used to avoid data duplication in the serialized object.
*/
declare function serializeGraph<Node, LinkProps, IncludeDefaultWeight extends boolean, NodeIdentity = Node>(graph: Graph<Node, LinkProps>, ...args: [
identityFn: (node: NoInfer<Node>) => NodeIdentity,
SerializeGraphOptions<IncludeDefaultWeight>?
] | [SerializeGraphOptions<IncludeDefaultWeight>?]): Serialized<Node, LinkProps, NodeIdentity>;
declare function deserializeGraph<Node, LinkProps, NodeIdentity>(...args: Node extends object ? [
data: SerializedInput<Node, LinkProps>,
identityFn: (node: NoInfer<Node>) => NodeIdentity
] : [data: SerializedInput<Node, LinkProps>]): Graph<Node, LinkProps>;
/**
* Returns all the nodes matching your function.
*/
declare function findNodes<Node>(graph: Graph<Node, any>, fn: (node: NoInfer<Node>) => boolean): Node[];
/**
* Return the node matching your function. Throws if none is found or if more than one node if found.
*/
declare function getNode<Node>(graph: Graph<Node, any>, fn: (node: NoInfer<Node>) => boolean): Node;
/**
* Return the first node matching your function and throws if none is found.
*/
declare function getFirstNode<Node>(graph: Graph<Node, any>, fn: (node: NoInfer<Node>) => boolean): Node;
export { CycleError, type Edge, type EdgeWeight, Graph, type NextWeightFnParams, type Serialized, type SerializedInput, cloneGraph, depthFirstSearch, deserializeGraph, findNodes, getFirstNode, getNode, hasCycle, indegree, lowestCommonAncestors, outdegree, serializeGraph, shortestPath, shortestPaths, topologicalSort };