@graphty/algorithms
Version:
Graph algorithms library for browser environments implemented in TypeScript
296 lines • 8.71 kB
JavaScript
/**
* Core Graph data structure for the Graphty Algorithms library
*
* Provides efficient graph representation with support for both directed and undirected graphs.
* Uses adjacency lists for optimal performance with sparse graphs.
*/
export class Graph {
constructor(config = {}) {
this.config = {
directed: false,
allowSelfLoops: true,
allowParallelEdges: false,
...config,
};
this.nodeMap = new Map();
this.adjacencyList = new Map();
this.incomingEdges = new Map();
this.edgeCount = 0;
}
/**
* Add a node to the graph
*/
addNode(id, data) {
if (!this.nodeMap.has(id)) {
this.nodeMap.set(id, { id, data });
this.adjacencyList.set(id, new Map());
if (this.config.directed) {
this.incomingEdges.set(id, new Map());
}
}
}
/**
* Remove a node from the graph
*/
removeNode(id) {
if (!this.nodeMap.has(id)) {
return false;
}
// Remove all edges connected to this node
const outgoingEdges = this.adjacencyList.get(id);
if (outgoingEdges) {
for (const targetId of Array.from(outgoingEdges.keys())) {
this.removeEdge(id, targetId);
}
}
if (this.config.directed) {
const incomingEdges = this.incomingEdges.get(id);
if (incomingEdges) {
for (const sourceId of Array.from(incomingEdges.keys())) {
this.removeEdge(sourceId, id);
}
}
}
else {
// For undirected graphs, also remove edges where this node is the target
for (const [nodeId, edges] of Array.from(this.adjacencyList)) {
if (edges.has(id)) {
this.removeEdge(nodeId, id);
}
}
}
// Remove the node itself
this.nodeMap.delete(id);
this.adjacencyList.delete(id);
if (this.config.directed) {
this.incomingEdges.delete(id);
}
return true;
}
/**
* Add an edge to the graph
*/
addEdge(source, target, weight = 1, data) {
// Ensure both nodes exist
this.addNode(source);
this.addNode(target);
// Check self-loops
if (!this.config.allowSelfLoops && source === target) {
throw new Error("Self-loops are not allowed in this graph");
}
// Check parallel edges
if (!this.config.allowParallelEdges && this.hasEdge(source, target)) {
throw new Error("Parallel edges are not allowed in this graph");
}
const edge = { source, target, weight, data };
// Add to adjacency list
const sourceAdjacency = this.adjacencyList.get(source);
if (sourceAdjacency) {
sourceAdjacency.set(target, edge);
}
if (this.config.directed) {
// For directed graphs, add to incoming edges list
const targetIncoming = this.incomingEdges.get(target);
if (targetIncoming) {
targetIncoming.set(source, edge);
}
}
else {
// For undirected graphs, add the reverse edge
if (source !== target) {
const reverseEdge = { source: target, target: source, weight, data };
const targetAdjacency = this.adjacencyList.get(target);
if (targetAdjacency) {
targetAdjacency.set(source, reverseEdge);
}
}
}
this.edgeCount++;
}
/**
* Remove an edge from the graph
*/
removeEdge(source, target) {
const sourceEdges = this.adjacencyList.get(source);
if (!sourceEdges?.has(target)) {
return false;
}
sourceEdges.delete(target);
if (this.config.directed) {
const targetIncoming = this.incomingEdges.get(target);
if (targetIncoming) {
targetIncoming.delete(source);
}
}
else {
// For undirected graphs, remove the reverse edge
const targetEdges = this.adjacencyList.get(target);
if (targetEdges) {
targetEdges.delete(source);
}
}
this.edgeCount--;
return true;
}
/**
* Check if a node exists in the graph
*/
hasNode(id) {
return this.nodeMap.has(id);
}
/**
* Check if an edge exists in the graph
*/
hasEdge(source, target) {
const sourceEdges = this.adjacencyList.get(source);
return sourceEdges ? sourceEdges.has(target) : false;
}
/**
* Get a node by ID
*/
getNode(id) {
return this.nodeMap.get(id);
}
/**
* Get an edge by source and target
*/
getEdge(source, target) {
const sourceEdges = this.adjacencyList.get(source);
return sourceEdges ? sourceEdges.get(target) : undefined;
}
/**
* Get the number of nodes in the graph
*/
get nodeCount() {
return this.nodeMap.size;
}
/**
* Get the number of edges in the graph
*/
get totalEdgeCount() {
return this.edgeCount;
}
/**
* Check if the graph is directed
*/
get isDirected() {
return this.config.directed;
}
/**
* Get all nodes in the graph
*/
nodes() {
return this.nodeMap.values();
}
/**
* Get all edges in the graph
*/
*edges() {
for (const [source, edges] of this.adjacencyList) {
for (const edge of edges.values()) {
// For undirected graphs, only yield each edge once
if (!this.config.directed && source > edge.target) {
continue;
}
yield edge;
}
}
}
/**
* Get neighbors of a node (outgoing edges)
*/
neighbors(nodeId) {
const edges = this.adjacencyList.get(nodeId);
return edges ? edges.keys() : new Map().keys();
}
/**
* Get incoming neighbors of a node (directed graphs only)
*/
inNeighbors(nodeId) {
if (!this.config.directed) {
return this.neighbors(nodeId);
}
const edges = this.incomingEdges.get(nodeId);
return edges ? edges.keys() : new Map().keys();
}
/**
* Get outgoing neighbors of a node
*/
outNeighbors(nodeId) {
return this.neighbors(nodeId);
}
/**
* Get the degree of a node
*/
degree(nodeId) {
if (this.config.directed) {
return this.inDegree(nodeId) + this.outDegree(nodeId);
}
const edges = this.adjacencyList.get(nodeId);
return edges ? edges.size : 0;
}
/**
* Get the in-degree of a node
*/
inDegree(nodeId) {
if (!this.config.directed) {
return this.degree(nodeId);
}
const edges = this.incomingEdges.get(nodeId);
return edges ? edges.size : 0;
}
/**
* Get the out-degree of a node
*/
outDegree(nodeId) {
const edges = this.adjacencyList.get(nodeId);
return edges ? edges.size : 0;
}
/**
* Create a copy of the graph
*/
clone() {
const cloned = new Graph(this.config);
// Copy nodes
for (const node of this.nodeMap.values()) {
cloned.addNode(node.id, node.data ? { ...node.data } : undefined);
}
// Copy edges
for (const edge of this.edges()) {
cloned.addEdge(edge.source, edge.target, edge.weight, edge.data ? { ...edge.data } : undefined);
}
return cloned;
}
/**
* Get graph configuration
*/
getConfig() {
return { ...this.config };
}
/**
* Clear all nodes and edges from the graph
*/
clear() {
this.nodeMap.clear();
this.adjacencyList.clear();
this.incomingEdges.clear();
this.edgeCount = 0;
}
/**
* Get the number of unique edges in the graph
* For undirected graphs, each edge is counted once
*/
get uniqueEdgeCount() {
if (this.config.directed) {
return this.edgeCount;
}
// For undirected graphs, we need to count each edge only once
let count = 0;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
for (const _edge of this.edges()) {
count++;
}
return count;
}
}
//# sourceMappingURL=graph.js.map