npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@haragei/dag

v1.1.0

Published

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

Downloads

9

Readme

@haragei/dag

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.

Features

  • 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 node v, u comes before v in the ordering. This is particularly useful in scenarios like task scheduling, where certain tasks must precede others.

Installation

npm install @haragei/dag
pnpm add @haragei/dag
yarn add @haragei/dag

Usage

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']

API Reference

DAG

A Directed Acyclic Graph structure with online cycle detection and topological ordering.

Create / Copy:

Properties:

Iteration:

Nodes:

Edges:

Sorting / Ordering:

Subgraphs:

Predecessors / Successors:

Merging:

constructor(initialNodes?: Iterable<T>)

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.

copy(): DAG<T>

Creates a new, independent copy of this DAG.

Returns: Clone of this DAG.

order: 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.

Returns: An array of all graph nodes in a topological order.

size: number

Returns the number of nodes in the graph.

Returns: Number of nodes in the graph.

[Symbol.iterator](): IterableIterator<T>

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.

keys(): IterableIterator<T>

Returns an iterator that yields all nodes in the graph in a topological order.

Returns: An iterator over all graph nodes in the graph.

clear(): this

Removes all nodes (and their edges) from the graph.

Returns: This DAG.

add(node: T): this

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.

has(node: T): boolean

Checks if a specific node exists in the graph.

Parameters:

  • node: The node to check.

Returns: true if the node exists, false otherwise.

delete(node: T): this

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.

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 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.

tryAddEdge(from: T, to: T): boolean

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.

hasEdge(from: T, to: T): boolean

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.

hasPath(from: T, to: T): boolean

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.

deleteEdge(from: T, to: T): this

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.

deleteOutgoingEdgesOf(node: T): this

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.

deleteIncomingEdgesOf(node: T): this

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.

getNodeOrder(...nodes: T[]): T[]

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.

sortNodes(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.

Parameters:

  • nodes: The nodes to sort.

Returns: The input array of nodes, sorted in a topological order.

subGraphs(): T[][]

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.

parallelize(): ParallelCollection<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:

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.

getImmediatePredecessorsOf(...nodes: T[]): Set<T>

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.

getOrderedImmediatePredecessorsOf(...nodes: T[]): Iterable<T>

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.

getPredecessorsOf(...nodes: T[]): Set<T>

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.

getOrderedPredecessorsOf(...nodes: T[]): Iterable<T>

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.

getImmediateSuccessorsOf(...nodes: T[]): Set<T>

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.

getOrderedImmediateSuccessorsOf(...nodes: T[]): Iterable<T>

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.

getSuccessorsOf(...nodes: T[]): Set<T>

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.

getOrderedSuccessorsOf(...nodes: T[]): Iterable<T>

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.

mergeNodes(a: T, b: T): this

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.

Parameters:

  • a: The first node to merge.
  • b: The second node to merge.

Returns: This DAG.

tryMergeNodes(a: T, b: 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), 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.

References

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]

Contributing

Contributions are welcome! For feature requests and bug reports, please [submit an issue](