graph-data-structure
Version:
A graph data structure with topological sort.
222 lines (146 loc) • 9.33 kB
Markdown
# graph-data-structure
A [graph data structure](<https://en.wikipedia.org/wiki/Graph_(abstract_data_type)>) with [topological sort](https://en.wikipedia.org/wiki/Topological_sorting).
This library provides a minimalist implementation of a directed graph data structure. Nodes are represented by unique strings or any other object. Internally, an [adjacency list](https://en.wikipedia.org/wiki/Adjacency_list) is used to represent nodes and edges.
The primary use case for this library is in implementing [dataflow programming](https://en.wikipedia.org/wiki/Dataflow_programming) or [reactive programming](https://en.wikipedia.org/wiki/Reactive_programming). The key algorithm necessary for these is topological sorting, to get an ordering of nodes such that for each edge (**u** -> **v**), **u** comes before **v** in the sorted order. The topological sorting algorithm exposed here has modifications useful for computing the order in which functions in a data flow graph should be executed, namely specifying source nodes for propagation and specifying to exclude the source nodes themselves from the result.
**Table of Contents**
- [Installing](#installing)
- [Examples](#examples)
- [ABC](#abc)
- [Getting Dressed](#getting-dressed)
- [API Reference](#api-reference)
## Installing
This library is distributed only via [NPM](npmjs.com). Install by running
`npm install graph-data-structure`
Require it in your code like this.
```javascript
import {
Graph,
serializeGraph,
deserializeGraph,
topologicalSort,
shortestPath,
} from 'graph-data-structure';
```
## Examples
### ABC
Start by creating a new **[Graph](#graph)** object.
```javascript
var graph = new Graph();
```
Add some nodes and edges with **[addNode](#add-node)** and **[addEdge](#add-edge)**.
```javascript
graph.addNode('a');
graph.addNode('b');
graph.addEdge('a', 'b');
```
Nodes are added implicitly when edges are added.
```javascript
graph.addEdge('b', 'c');
```
Now we have the following graph. <img src="https://cloud.githubusercontent.com/assets/68416/15385597/44a10522-1dc0-11e6-9054-2150f851db46.png">
[Topological sorting](https://en.wikipedia.org/wiki/Topological_sorting) can be done by invoking the standalone function **[topologicalSort](#topological-sort)** like this.
```javascript
topologicalSort(graph); // Returns ["a", "b", "c"]
```
### Getting Dressed
Here's an example of topological sort with getting dressed (from Cormen et al. "Introduction to Algorithms" page 550).
<p align="center">
<img src="https://cloud.githubusercontent.com/assets/68416/15385742/36f76410-1dc1-11e6-9fac-a8e41379c795.png">
</p>
```javascript
const graph = new Graph();
graph
.addEdge('socks', 'shoes')
.addEdge('shirt', 'belt')
.addEdge('shirt', 'tie')
.addEdge('tie', 'jacket')
.addEdge('belt', 'jacket')
.addEdge('pants', 'shoes')
.addEdge('underpants', 'pants')
.addEdge('pants', 'belt');
// prints [ "underpants", "pants", "shirt", "tie", "belt", "jacket", "socks", "shoes" ]
console.log(topologicalSort(graph));
```
For more detailed example code that shows more methods, have a look at the [tests](https://github.com/datavis-tech/graph-data-structure/blob/master/test.js).
## API Reference
- [Creating a Graph](#creating-a-graph)
- [Adding and Removing Nodes](#adding-and-removing-nodes)
- [Adding and Removing Edges](#adding-and-removing-edges)
- [Querying the Graph](#querying-the-graph)
- [Serialization](#serialization)
- [Graph Algorithms](#graph-algorithms)
### Creating a Graph
<a name="graph" href="#graph">#</a> <b>Graph</b>([<i>serialized</i>])
Constructs an instance of the graph data structure.
The optional argument _serialized_ is a serialized graph that may have been generated by **[serializeGraph](#serializeGraph)**. If _serialized_ is present, it is deserialized by invoking **[deserializeGraph(mySerializedObject)](#deserializeGraph)**.
### Adding and Removing Nodes
<a name="add-node" href="#add-node">#</a> <i>graph</i>.<b>addNode</b>(<i>node</i>)
Adds a node to the graph. Returns _graph_ to support method chaining. If the given _node_ was already added to the graph, this function does nothing.
<a name="remove-node" href="#remove-node">#</a> <i>graph</i>.<b>removeNode</b>(<i>node</i>)
Removes the specified node. Returns _graph_ to support method chaining. The argument _node_ is a string or object identifier for the node to remove. This function also removes all edges connected to the specified node, both incoming and outgoing.
**Note:** You have to remove them using the exact same reference as when they were created. One can use getNode() to retrieve such reference.
### Adding and Removing Edges
<a name="add-edge" href="#add-edge">#</a> <i>graph</i>.<b>addEdge</b>(<i>u</i>, <i>v</i>[,<i>weight</i>])
Adds an edge from node _u_ to node _v_. Returns _graph_ to support method chaining. The arguments _u_ and _v_ are node references (either objects or strings). This function also adds _u_ and _v_ as nodes if they were not already added.
The last argument _weight_ (optional) specifies the weight of this edge.
<a name="remove-edge" href="#remove-edge">#</a> <i>graph</i>.<b>removeEdge</b>(<i>u</i>, <i>v</i>)
Removes the edge from node _u_ to node _v_. Returns _graph_ to support method chaining. The arguments _u_ and _v_ are node references. This function does not remove the nodes _u_ and _v_. Does nothing if the edge does not exist.
<a name="has-edge" href="#has-edge">#</a> <i>graph</i>.<b>hasEdge</b>(<i>u</i>, <i>v</i>)
Returns `true` if there exists an edge from node _u_ to node _v_. Returns `false` otherwise.
### Working with Edge Weights
<a name="set-edge-weight" href="#set-edge-weight">#</a> <i>graph</i>.<b>setEdgeWeight</b>(<i>u</i>, <i>v</i>, <i>weight</i>)
Sets the _weight_ (a number) of the edge from node _u_ to node _v_.
<a name="get-edge-weight" href="#get-edge-weight">#</a> <i>graph</i>.<b>getEdgeWeight</b>(<i>u</i>, <i>v</i>)
Gets the _weight_ of the edge from node _u_ to node _v_. If no weight was previously set on this edge, then the value 1 is returned.
### Querying the Graph
<a name="adjacent" href="#adjacent">#</a> <i>graph</i>.<b>adjacent</b>(<i>node</i>)
Gets the adjacent node list for the specified node. The argument _node_ is a node reference (object or string). Returns a `Set` of adjacent node references or `undefined` if the node is not found.
### Serialization
<a name="serializeGraph" href="#serializeGraph">#</a> <b>serializeGraph</b>(<i>graph</i>)
Serializes the graph. Returns an object with the following properties.
- `nodes` An array of objects, each representing a node reference.
- `links` An array of objects representing edges, each with the following properties.
- `source` The node reference of the source node (**u**).
- `target` The node reference of the target node (**v**).
- `weight` The weight of the edge between the source and target nodes.
Here's example code for serializing a graph.
```javascript
var graph = new Graph();
graph.addEdge('a', 'b');
graph.addEdge('b', 'c');
var serialized = serializeGraph(graph);
```
<a name="deserializeGraph" href="#deserializeGraph">#</a> <b>deserializeGraph</b>(<i>serialized</i>)
Deserializes the given serialized graph. Returns a new _graph_. The argument _serialized_ is a graph representation with the structure described in **[serializeGraph](#serializeGraph)**.
### Graph Algorithms
<a name="topological-sort" href="#topological-sort">#</a> <b>topologicalSort</b>(<i>graph</i>)
Performs [Topological Sort](
https://en.wikipedia.org/wiki/Topological_sorting). Returns an array of node identifier strings. The returned array includes nodes in topologically sorted order. This means that for each visited edge (**u** -> **v**), **u** comes before **v** in the topologically sorted order.
Note: this function raises a `CycleError` when the input is not a DAG.
<a name="shortest-path" href="#shortest-path">#</a> <b>shortestPath</b>(<i>graph</i>, <i>sourceNode</i>, <i>destinationNode</i>)
Performs [Dijkstra's Algorithm](https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm). Returns an object with two properties: `nodes`, an array of node references representing the path, and `weight`, the total weight of the path.
```javascript
var result = shortestPath(graph, 'a', 'c');
console.log(result.nodes); // Prints the array of nodes in the shortest path
console.log(result.weight); // Prints the total weight of the path
```
<a name="shortest-path" href="#shortest-path">#</a> <b>shortestPath</b>(<i>graph</i>, <i>sourceNode</i>, <i>destinationNode</i>, <i>nextWeightFn</i>)
Calculates the weight based on the custom function.
```javascript
import type { NextWeightFnParams } from '../../types.js';
function multiplyWeightFunction(wp: NextWeightFnParams): number {
if (wp.currentPathWeight === undefined) {
return wp.edgeWeight;
}
return wp.edgeWeight * wp.currentPathWeight;
}
var result = shortestPath(graph, 'a', 'c', multiplyWeightFunction);
console.log(result.nodes); // Prints the array of nodes in the shortest path
console.log(result.weight); // Prints the total weight of the path
```
<p align="center">
<a href="https://datavis.tech/">
<img src="https://cloud.githubusercontent.com/assets/68416/15298394/a7a0a66a-1bbc-11e6-9636-367bed9165fc.png">
</a>
</p>
```