@graphty/algorithms 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +193 -0
  3. package/dist/src/algorithms/centrality/betweenness.d.ts +34 -0
  4. package/dist/src/algorithms/centrality/betweenness.d.ts.map +1 -0
  5. package/dist/src/algorithms/centrality/betweenness.js +223 -0
  6. package/dist/src/algorithms/centrality/betweenness.js.map +1 -0
  7. package/dist/src/algorithms/centrality/closeness.d.ts +43 -0
  8. package/dist/src/algorithms/centrality/closeness.d.ts.map +1 -0
  9. package/dist/src/algorithms/centrality/closeness.js +202 -0
  10. package/dist/src/algorithms/centrality/closeness.js.map +1 -0
  11. package/dist/src/algorithms/centrality/degree.d.ts +17 -0
  12. package/dist/src/algorithms/centrality/degree.d.ts.map +1 -0
  13. package/dist/src/algorithms/centrality/degree.js +86 -0
  14. package/dist/src/algorithms/centrality/degree.js.map +1 -0
  15. package/dist/src/algorithms/centrality/index.d.ts +11 -0
  16. package/dist/src/algorithms/centrality/index.d.ts.map +1 -0
  17. package/dist/src/algorithms/centrality/index.js +8 -0
  18. package/dist/src/algorithms/centrality/index.js.map +1 -0
  19. package/dist/src/algorithms/centrality/pagerank.d.ts +74 -0
  20. package/dist/src/algorithms/centrality/pagerank.d.ts.map +1 -0
  21. package/dist/src/algorithms/centrality/pagerank.js +191 -0
  22. package/dist/src/algorithms/centrality/pagerank.js.map +1 -0
  23. package/dist/src/algorithms/components/connected.d.ts +58 -0
  24. package/dist/src/algorithms/components/connected.d.ts.map +1 -0
  25. package/dist/src/algorithms/components/connected.js +234 -0
  26. package/dist/src/algorithms/components/connected.js.map +1 -0
  27. package/dist/src/algorithms/components/index.d.ts +5 -0
  28. package/dist/src/algorithms/components/index.d.ts.map +1 -0
  29. package/dist/src/algorithms/components/index.js +5 -0
  30. package/dist/src/algorithms/components/index.js.map +1 -0
  31. package/dist/src/algorithms/index.d.ts +8 -0
  32. package/dist/src/algorithms/index.d.ts.map +1 -0
  33. package/dist/src/algorithms/index.js +12 -0
  34. package/dist/src/algorithms/index.js.map +1 -0
  35. package/dist/src/algorithms/shortest-path/bellman-ford.d.ts +52 -0
  36. package/dist/src/algorithms/shortest-path/bellman-ford.d.ts.map +1 -0
  37. package/dist/src/algorithms/shortest-path/bellman-ford.js +131 -0
  38. package/dist/src/algorithms/shortest-path/bellman-ford.js.map +1 -0
  39. package/dist/src/algorithms/shortest-path/dijkstra.d.ts +26 -0
  40. package/dist/src/algorithms/shortest-path/dijkstra.d.ts.map +1 -0
  41. package/dist/src/algorithms/shortest-path/dijkstra.js +199 -0
  42. package/dist/src/algorithms/shortest-path/dijkstra.js.map +1 -0
  43. package/dist/src/algorithms/shortest-path/index.d.ts +7 -0
  44. package/dist/src/algorithms/shortest-path/index.d.ts.map +1 -0
  45. package/dist/src/algorithms/shortest-path/index.js +6 -0
  46. package/dist/src/algorithms/shortest-path/index.js.map +1 -0
  47. package/dist/src/algorithms/traversal/bfs.d.ts +25 -0
  48. package/dist/src/algorithms/traversal/bfs.d.ts.map +1 -0
  49. package/dist/src/algorithms/traversal/bfs.js +189 -0
  50. package/dist/src/algorithms/traversal/bfs.js.map +1 -0
  51. package/dist/src/algorithms/traversal/dfs.d.ts +32 -0
  52. package/dist/src/algorithms/traversal/dfs.d.ts.map +1 -0
  53. package/dist/src/algorithms/traversal/dfs.js +259 -0
  54. package/dist/src/algorithms/traversal/dfs.js.map +1 -0
  55. package/dist/src/algorithms/traversal/index.d.ts +7 -0
  56. package/dist/src/algorithms/traversal/index.d.ts.map +1 -0
  57. package/dist/src/algorithms/traversal/index.js +6 -0
  58. package/dist/src/algorithms/traversal/index.js.map +1 -0
  59. package/dist/src/core/graph.d.ts +100 -0
  60. package/dist/src/core/graph.d.ts.map +1 -0
  61. package/dist/src/core/graph.js +276 -0
  62. package/dist/src/core/graph.js.map +1 -0
  63. package/dist/src/data-structures/index.d.ts +6 -0
  64. package/dist/src/data-structures/index.d.ts.map +1 -0
  65. package/dist/src/data-structures/index.js +6 -0
  66. package/dist/src/data-structures/index.js.map +1 -0
  67. package/dist/src/data-structures/priority-queue.d.ts +60 -0
  68. package/dist/src/data-structures/priority-queue.d.ts.map +1 -0
  69. package/dist/src/data-structures/priority-queue.js +152 -0
  70. package/dist/src/data-structures/priority-queue.js.map +1 -0
  71. package/dist/src/data-structures/union-find.d.ts +54 -0
  72. package/dist/src/data-structures/union-find.d.ts.map +1 -0
  73. package/dist/src/data-structures/union-find.js +145 -0
  74. package/dist/src/data-structures/union-find.js.map +1 -0
  75. package/dist/src/index.d.ts +11 -0
  76. package/dist/src/index.d.ts.map +1 -0
  77. package/dist/src/index.js +13 -0
  78. package/dist/src/index.js.map +1 -0
  79. package/dist/src/types/index.d.ts +74 -0
  80. package/dist/src/types/index.d.ts.map +1 -0
  81. package/dist/src/types/index.js +5 -0
  82. package/dist/src/types/index.js.map +1 -0
  83. package/dist/test/browser/basic.test.d.ts +2 -0
  84. package/dist/test/browser/basic.test.d.ts.map +1 -0
  85. package/dist/test/browser/basic.test.js +70 -0
  86. package/dist/test/browser/basic.test.js.map +1 -0
  87. package/dist/test/unit/bellman-ford.test.d.ts +2 -0
  88. package/dist/test/unit/bellman-ford.test.d.ts.map +1 -0
  89. package/dist/test/unit/bellman-ford.test.js +231 -0
  90. package/dist/test/unit/bellman-ford.test.js.map +1 -0
  91. package/dist/test/unit/betweenness-centrality.test.d.ts +2 -0
  92. package/dist/test/unit/betweenness-centrality.test.d.ts.map +1 -0
  93. package/dist/test/unit/betweenness-centrality.test.js +311 -0
  94. package/dist/test/unit/betweenness-centrality.test.js.map +1 -0
  95. package/dist/test/unit/bfs.test.d.ts +2 -0
  96. package/dist/test/unit/bfs.test.d.ts.map +1 -0
  97. package/dist/test/unit/bfs.test.js +277 -0
  98. package/dist/test/unit/bfs.test.js.map +1 -0
  99. package/dist/test/unit/closeness.test.d.ts +2 -0
  100. package/dist/test/unit/closeness.test.d.ts.map +1 -0
  101. package/dist/test/unit/closeness.test.js +266 -0
  102. package/dist/test/unit/closeness.test.js.map +1 -0
  103. package/dist/test/unit/connected-components.test.d.ts +2 -0
  104. package/dist/test/unit/connected-components.test.d.ts.map +1 -0
  105. package/dist/test/unit/connected-components.test.js +355 -0
  106. package/dist/test/unit/connected-components.test.js.map +1 -0
  107. package/dist/test/unit/degree-centrality.test.d.ts +2 -0
  108. package/dist/test/unit/degree-centrality.test.d.ts.map +1 -0
  109. package/dist/test/unit/degree-centrality.test.js +260 -0
  110. package/dist/test/unit/degree-centrality.test.js.map +1 -0
  111. package/dist/test/unit/dfs.test.d.ts +2 -0
  112. package/dist/test/unit/dfs.test.d.ts.map +1 -0
  113. package/dist/test/unit/dfs.test.js +350 -0
  114. package/dist/test/unit/dfs.test.js.map +1 -0
  115. package/dist/test/unit/dijkstra.test.d.ts +2 -0
  116. package/dist/test/unit/dijkstra.test.d.ts.map +1 -0
  117. package/dist/test/unit/dijkstra.test.js +311 -0
  118. package/dist/test/unit/dijkstra.test.js.map +1 -0
  119. package/dist/test/unit/graph.test.d.ts +2 -0
  120. package/dist/test/unit/graph.test.d.ts.map +1 -0
  121. package/dist/test/unit/graph.test.js +175 -0
  122. package/dist/test/unit/graph.test.js.map +1 -0
  123. package/dist/test/unit/pagerank.test.d.ts +2 -0
  124. package/dist/test/unit/pagerank.test.d.ts.map +1 -0
  125. package/dist/test/unit/pagerank.test.js +281 -0
  126. package/dist/test/unit/pagerank.test.js.map +1 -0
  127. package/dist/test/unit/priority-queue.test.d.ts +2 -0
  128. package/dist/test/unit/priority-queue.test.d.ts.map +1 -0
  129. package/dist/test/unit/priority-queue.test.js +189 -0
  130. package/dist/test/unit/priority-queue.test.js.map +1 -0
  131. package/dist/test/unit/union-find.test.d.ts +2 -0
  132. package/dist/test/unit/union-find.test.d.ts.map +1 -0
  133. package/dist/test/unit/union-find.test.js +245 -0
  134. package/dist/test/unit/union-find.test.js.map +1 -0
  135. package/package.json +100 -0
  136. package/src/algorithms/centrality/betweenness.ts +303 -0
  137. package/src/algorithms/centrality/closeness.ts +296 -0
  138. package/src/algorithms/centrality/degree.ts +105 -0
  139. package/src/algorithms/centrality/index.ts +11 -0
  140. package/src/algorithms/centrality/pagerank.ts +302 -0
  141. package/src/algorithms/components/connected.ts +298 -0
  142. package/src/algorithms/components/index.ts +17 -0
  143. package/src/algorithms/index.ts +15 -0
  144. package/src/algorithms/shortest-path/bellman-ford.ts +214 -0
  145. package/src/algorithms/shortest-path/dijkstra.ts +256 -0
  146. package/src/algorithms/shortest-path/index.ts +7 -0
  147. package/src/algorithms/traversal/bfs.ts +236 -0
  148. package/src/algorithms/traversal/dfs.ts +367 -0
  149. package/src/algorithms/traversal/index.ts +7 -0
  150. package/src/core/graph.ts +326 -0
  151. package/src/data-structures/index.ts +6 -0
  152. package/src/data-structures/priority-queue.ts +180 -0
  153. package/src/data-structures/union-find.ts +170 -0
  154. package/src/index.ts +35 -0
  155. package/src/types/index.ts +95 -0
@@ -0,0 +1,367 @@
1
+ import {Graph} from "../../core/graph.js";
2
+ import type {NodeId, TraversalOptions, TraversalResult} from "../../types/index.js";
3
+
4
+ /**
5
+ * Depth-First Search (DFS) implementation
6
+ *
7
+ * Explores graph by going as deep as possible before backtracking.
8
+ * Useful for cycle detection, topological sorting, and connectivity analysis.
9
+ */
10
+
11
+ /**
12
+ * DFS traversal options
13
+ */
14
+ export interface DFSOptions extends TraversalOptions {
15
+ recursive?: boolean; // Use recursive implementation (default: false for browser safety)
16
+ preOrder?: boolean; // Visit nodes in pre-order (default: true)
17
+ }
18
+
19
+ /**
20
+ * Perform depth-first search starting from a given node
21
+ */
22
+ export function depthFirstSearch(
23
+ graph: Graph,
24
+ startNode: NodeId,
25
+ options: DFSOptions = {},
26
+ ): TraversalResult {
27
+ if (!graph.hasNode(startNode)) {
28
+ throw new Error(`Start node ${String(startNode)} not found in graph`);
29
+ }
30
+
31
+ const visited = new Set<NodeId>();
32
+ const order: NodeId[] = [];
33
+ const tree = new Map<NodeId, NodeId | null>();
34
+
35
+ if (options.recursive) {
36
+ dfsRecursive(graph, startNode, visited, order, tree, options, 0);
37
+ } else {
38
+ dfsIterative(graph, startNode, visited, order, tree, options);
39
+ }
40
+
41
+ return {visited, order, tree};
42
+ }
43
+
44
+ /**
45
+ * Iterative DFS implementation (safer for browsers)
46
+ */
47
+ function dfsIterative(
48
+ graph: Graph,
49
+ startNode: NodeId,
50
+ visited: Set<NodeId>,
51
+ order: NodeId[],
52
+ tree: Map<NodeId, NodeId | null>,
53
+ options: DFSOptions,
54
+ ): void {
55
+ if (options.preOrder === false) {
56
+ // For post-order, use a simpler recursive approach to ensure correctness
57
+ dfsRecursive(graph, startNode, visited, order, tree, options, 0, null);
58
+ return;
59
+ }
60
+
61
+ const stack: {node: NodeId, parent: NodeId | null, depth: number}[] = [];
62
+ stack.push({node: startNode, parent: null, depth: 0});
63
+
64
+ while (stack.length > 0) {
65
+ const current = stack.pop();
66
+ if (!current) {
67
+ break;
68
+ }
69
+
70
+ if (!visited.has(current.node)) {
71
+ visited.add(current.node);
72
+ tree.set(current.node, current.parent);
73
+
74
+ // Pre-order processing
75
+ order.push(current.node);
76
+
77
+ // Call visitor callback if provided
78
+ if (options.visitCallback) {
79
+ options.visitCallback(current.node, current.depth);
80
+ }
81
+
82
+ // Early termination if target found
83
+ if (options.targetNode && current.node === options.targetNode) {
84
+ break;
85
+ }
86
+
87
+ // Add neighbors to stack in reverse order to maintain left-to-right traversal
88
+ const neighbors = Array.from(graph.neighbors(current.node));
89
+ for (let i = neighbors.length - 1; i >= 0; i--) {
90
+ const neighbor = neighbors[i];
91
+ if (neighbor !== undefined && !visited.has(neighbor)) {
92
+ stack.push({node: neighbor, parent: current.node, depth: current.depth + 1});
93
+ }
94
+ }
95
+ }
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Recursive DFS implementation
101
+ */
102
+ function dfsRecursive(
103
+ graph: Graph,
104
+ node: NodeId,
105
+ visited: Set<NodeId>,
106
+ order: NodeId[],
107
+ tree: Map<NodeId, NodeId | null>,
108
+ options: DFSOptions,
109
+ depth: number,
110
+ parent: NodeId | null = null,
111
+ ): void {
112
+ visited.add(node);
113
+ tree.set(node, parent);
114
+
115
+ // Pre-order processing
116
+ if (options.preOrder !== false) {
117
+ order.push(node);
118
+
119
+ if (options.visitCallback) {
120
+ options.visitCallback(node, depth);
121
+ }
122
+
123
+ // Early termination if target found
124
+ if (options.targetNode && node === options.targetNode) {
125
+ return;
126
+ }
127
+ }
128
+
129
+ // Recursively visit neighbors
130
+ for (const neighbor of graph.neighbors(node)) {
131
+ if (!visited.has(neighbor)) {
132
+ dfsRecursive(graph, neighbor, visited, order, tree, options, depth + 1, node);
133
+ }
134
+ }
135
+
136
+ // Post-order processing
137
+ if (options.preOrder === false) {
138
+ order.push(node);
139
+
140
+ if (options.visitCallback) {
141
+ options.visitCallback(node, depth);
142
+ }
143
+ }
144
+ }
145
+
146
+ /**
147
+ * Detect cycles in a graph using DFS
148
+ */
149
+ export function hasCycleDFS(graph: Graph): boolean {
150
+ const visited = new Set<NodeId>();
151
+
152
+ // Check each unvisited node for cycles
153
+ for (const node of graph.nodes()) {
154
+ if (!visited.has(node.id)) {
155
+ if (graph.isDirected) {
156
+ const recursionStack = new Set<NodeId>();
157
+ if (hasCycleUtilDirected(graph, node.id, visited, recursionStack)) {
158
+ return true;
159
+ }
160
+ } else {
161
+ if (hasCycleUtilUndirected(graph, node.id, visited, null)) {
162
+ return true;
163
+ }
164
+ }
165
+ }
166
+ }
167
+
168
+ return false;
169
+ }
170
+
171
+ /**
172
+ * Utility function for cycle detection in directed graphs
173
+ */
174
+ function hasCycleUtilDirected(
175
+ graph: Graph,
176
+ node: NodeId,
177
+ visited: Set<NodeId>,
178
+ recursionStack: Set<NodeId>,
179
+ ): boolean {
180
+ visited.add(node);
181
+ recursionStack.add(node);
182
+
183
+ // Check all neighbors
184
+ for (const neighbor of graph.neighbors(node)) {
185
+ if (!visited.has(neighbor)) {
186
+ if (hasCycleUtilDirected(graph, neighbor, visited, recursionStack)) {
187
+ return true;
188
+ }
189
+ } else if (recursionStack.has(neighbor)) {
190
+ // Back edge found - cycle detected
191
+ return true;
192
+ }
193
+ }
194
+
195
+ recursionStack.delete(node);
196
+ return false;
197
+ }
198
+
199
+ /**
200
+ * Utility function for cycle detection in undirected graphs
201
+ */
202
+ function hasCycleUtilUndirected(
203
+ graph: Graph,
204
+ node: NodeId,
205
+ visited: Set<NodeId>,
206
+ parent: NodeId | null,
207
+ ): boolean {
208
+ visited.add(node);
209
+
210
+ // Check all neighbors
211
+ for (const neighbor of graph.neighbors(node)) {
212
+ if (!visited.has(neighbor)) {
213
+ if (hasCycleUtilUndirected(graph, neighbor, visited, node)) {
214
+ return true;
215
+ }
216
+ } else if (neighbor !== parent) {
217
+ // Found a visited node that's not the parent - cycle detected
218
+ return true;
219
+ }
220
+ }
221
+
222
+ return false;
223
+ }
224
+
225
+ /**
226
+ * Topological sorting using DFS (for directed acyclic graphs)
227
+ */
228
+ export function topologicalSort(graph: Graph): NodeId[] | null {
229
+ if (!graph.isDirected) {
230
+ throw new Error("Topological sort requires a directed graph");
231
+ }
232
+
233
+ // First check if graph has cycles
234
+ if (hasCycleDFS(graph)) {
235
+ return null; // Cannot topologically sort a graph with cycles
236
+ }
237
+
238
+ const visited = new Set<NodeId>();
239
+ const stack: NodeId[] = [];
240
+
241
+ // Perform DFS from each unvisited node
242
+ for (const node of graph.nodes()) {
243
+ if (!visited.has(node.id)) {
244
+ topologicalSortUtil(graph, node.id, visited, stack);
245
+ }
246
+ }
247
+
248
+ // Return nodes in reverse order of finishing times
249
+ return stack.reverse();
250
+ }
251
+
252
+ /**
253
+ * Utility function for topological sorting
254
+ */
255
+ function topologicalSortUtil(
256
+ graph: Graph,
257
+ node: NodeId,
258
+ visited: Set<NodeId>,
259
+ stack: NodeId[],
260
+ ): void {
261
+ visited.add(node);
262
+
263
+ // Visit all neighbors first
264
+ for (const neighbor of graph.neighbors(node)) {
265
+ if (!visited.has(neighbor)) {
266
+ topologicalSortUtil(graph, neighbor, visited, stack);
267
+ }
268
+ }
269
+
270
+ // Add current node to stack after visiting all neighbors
271
+ stack.push(node);
272
+ }
273
+
274
+ /**
275
+ * Find strongly connected components using DFS (for directed graphs)
276
+ */
277
+ export function findStronglyConnectedComponents(graph: Graph): NodeId[][] {
278
+ if (!graph.isDirected) {
279
+ throw new Error("Strongly connected components require a directed graph");
280
+ }
281
+
282
+ const visited = new Set<NodeId>();
283
+ const finishOrder: NodeId[] = [];
284
+
285
+ // Step 1: Get nodes in order of finishing times
286
+ for (const node of graph.nodes()) {
287
+ if (!visited.has(node.id)) {
288
+ dfsFinishOrder(graph, node.id, visited, finishOrder);
289
+ }
290
+ }
291
+
292
+ // Step 2: Create transpose graph (reverse all edges)
293
+ const transposeGraph = createTransposeGraph(graph);
294
+
295
+ // Step 3: DFS on transpose graph in reverse finish order
296
+ const visited2 = new Set<NodeId>();
297
+ const components: NodeId[][] = [];
298
+
299
+ for (let i = finishOrder.length - 1; i >= 0; i--) {
300
+ const node = finishOrder[i];
301
+ if (node !== undefined && !visited2.has(node)) {
302
+ const component: NodeId[] = [];
303
+ dfsCollectComponent(transposeGraph, node, visited2, component);
304
+ components.push(component);
305
+ }
306
+ }
307
+
308
+ return components;
309
+ }
310
+
311
+ /**
312
+ * DFS to record finish order
313
+ */
314
+ function dfsFinishOrder(
315
+ graph: Graph,
316
+ node: NodeId,
317
+ visited: Set<NodeId>,
318
+ finishOrder: NodeId[],
319
+ ): void {
320
+ visited.add(node);
321
+
322
+ for (const neighbor of graph.neighbors(node)) {
323
+ if (!visited.has(neighbor)) {
324
+ dfsFinishOrder(graph, neighbor, visited, finishOrder);
325
+ }
326
+ }
327
+
328
+ finishOrder.push(node);
329
+ }
330
+
331
+ /**
332
+ * DFS to collect nodes in a component
333
+ */
334
+ function dfsCollectComponent(
335
+ graph: Graph,
336
+ node: NodeId,
337
+ visited: Set<NodeId>,
338
+ component: NodeId[],
339
+ ): void {
340
+ visited.add(node);
341
+ component.push(node);
342
+
343
+ for (const neighbor of graph.neighbors(node)) {
344
+ if (!visited.has(neighbor)) {
345
+ dfsCollectComponent(graph, neighbor, visited, component);
346
+ }
347
+ }
348
+ }
349
+
350
+ /**
351
+ * Create transpose (reverse) of a directed graph
352
+ */
353
+ function createTransposeGraph(graph: Graph): Graph {
354
+ const transpose = new Graph({directed: true});
355
+
356
+ // Add all nodes
357
+ for (const node of graph.nodes()) {
358
+ transpose.addNode(node.id, node.data);
359
+ }
360
+
361
+ // Add reverse edges
362
+ for (const edge of graph.edges()) {
363
+ transpose.addEdge(edge.target, edge.source, edge.weight, edge.data);
364
+ }
365
+
366
+ return transpose;
367
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Graph traversal algorithms
3
+ */
4
+
5
+ export {breadthFirstSearch, isBipartite, shortestPathBFS, singleSourceShortestPathBFS} from "./bfs.js";
6
+ export type {DFSOptions} from "./dfs.js";
7
+ export {depthFirstSearch, findStronglyConnectedComponents, hasCycleDFS, topologicalSort} from "./dfs.js";
@@ -0,0 +1,326 @@
1
+ import type {Edge, GraphConfig, Node, NodeId} from "../types/index.js";
2
+
3
+ /**
4
+ * Core Graph data structure for the Graphty Algorithms library
5
+ *
6
+ * Provides efficient graph representation with support for both directed and undirected graphs.
7
+ * Uses adjacency lists for optimal performance with sparse graphs.
8
+ */
9
+ export class Graph {
10
+ private nodeMap: Map<NodeId, Node>;
11
+ private adjacencyList: Map<NodeId, Map<NodeId, Edge>>;
12
+ private incomingEdges: Map<NodeId, Map<NodeId, Edge>>; // For directed graphs
13
+ private config: GraphConfig;
14
+ private edgeCount: number;
15
+
16
+ constructor(config: Partial<GraphConfig> = {}) {
17
+ this.config = {
18
+ directed: false,
19
+ allowSelfLoops: true,
20
+ allowParallelEdges: false,
21
+ ... config,
22
+ };
23
+ this.nodeMap = new Map();
24
+ this.adjacencyList = new Map();
25
+ this.incomingEdges = new Map();
26
+ this.edgeCount = 0;
27
+ }
28
+
29
+ /**
30
+ * Add a node to the graph
31
+ */
32
+ addNode(id: NodeId, data?: Record<string, unknown>): void {
33
+ if (!this.nodeMap.has(id)) {
34
+ this.nodeMap.set(id, {id, data});
35
+ this.adjacencyList.set(id, new Map());
36
+
37
+ if (this.config.directed) {
38
+ this.incomingEdges.set(id, new Map());
39
+ }
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Remove a node from the graph
45
+ */
46
+ removeNode(id: NodeId): boolean {
47
+ if (!this.nodeMap.has(id)) {
48
+ return false;
49
+ }
50
+
51
+ // Remove all edges connected to this node
52
+ const outgoingEdges = this.adjacencyList.get(id);
53
+ if (outgoingEdges) {
54
+ for (const targetId of outgoingEdges.keys()) {
55
+ this.removeEdge(id, targetId);
56
+ }
57
+ }
58
+
59
+ if (this.config.directed) {
60
+ const incomingEdges = this.incomingEdges.get(id);
61
+ if (incomingEdges) {
62
+ for (const sourceId of incomingEdges.keys()) {
63
+ this.removeEdge(sourceId, id);
64
+ }
65
+ }
66
+ } else {
67
+ // For undirected graphs, also remove edges where this node is the target
68
+ for (const [nodeId, edges] of this.adjacencyList) {
69
+ if (edges.has(id)) {
70
+ this.removeEdge(nodeId, id);
71
+ }
72
+ }
73
+ }
74
+
75
+ // Remove the node itself
76
+ this.nodeMap.delete(id);
77
+ this.adjacencyList.delete(id);
78
+
79
+ if (this.config.directed) {
80
+ this.incomingEdges.delete(id);
81
+ }
82
+
83
+ return true;
84
+ }
85
+
86
+ /**
87
+ * Add an edge to the graph
88
+ */
89
+ addEdge(source: NodeId, target: NodeId, weight = 1, data?: Record<string, unknown>): void {
90
+ // Ensure both nodes exist
91
+ this.addNode(source);
92
+ this.addNode(target);
93
+
94
+ // Check self-loops
95
+ if (!this.config.allowSelfLoops && source === target) {
96
+ throw new Error("Self-loops are not allowed in this graph");
97
+ }
98
+
99
+ // Check parallel edges
100
+ if (!this.config.allowParallelEdges && this.hasEdge(source, target)) {
101
+ throw new Error("Parallel edges are not allowed in this graph");
102
+ }
103
+
104
+ const edge: Edge = {source, target, weight, data};
105
+
106
+ // Add to adjacency list
107
+ const sourceAdjacency = this.adjacencyList.get(source);
108
+
109
+ if (sourceAdjacency) {
110
+ sourceAdjacency.set(target, edge);
111
+ }
112
+
113
+ if (this.config.directed) {
114
+ // For directed graphs, add to incoming edges list
115
+ const targetIncoming = this.incomingEdges.get(target);
116
+
117
+ if (targetIncoming) {
118
+ targetIncoming.set(source, edge);
119
+ }
120
+ } else {
121
+ // For undirected graphs, add the reverse edge
122
+ if (source !== target) {
123
+ const reverseEdge: Edge = {source: target, target: source, weight, data};
124
+ const targetAdjacency = this.adjacencyList.get(target);
125
+
126
+ if (targetAdjacency) {
127
+ targetAdjacency.set(source, reverseEdge);
128
+ }
129
+ }
130
+ }
131
+
132
+ this.edgeCount++;
133
+ }
134
+
135
+ /**
136
+ * Remove an edge from the graph
137
+ */
138
+ removeEdge(source: NodeId, target: NodeId): boolean {
139
+ const sourceEdges = this.adjacencyList.get(source);
140
+ if (!sourceEdges?.has(target)) {
141
+ return false;
142
+ }
143
+
144
+ sourceEdges.delete(target);
145
+
146
+ if (this.config.directed) {
147
+ const targetIncoming = this.incomingEdges.get(target);
148
+ if (targetIncoming) {
149
+ targetIncoming.delete(source);
150
+ }
151
+ } else {
152
+ // For undirected graphs, remove the reverse edge
153
+ const targetEdges = this.adjacencyList.get(target);
154
+ if (targetEdges) {
155
+ targetEdges.delete(source);
156
+ }
157
+ }
158
+
159
+ this.edgeCount--;
160
+ return true;
161
+ }
162
+
163
+ /**
164
+ * Check if a node exists in the graph
165
+ */
166
+ hasNode(id: NodeId): boolean {
167
+ return this.nodeMap.has(id);
168
+ }
169
+
170
+ /**
171
+ * Check if an edge exists in the graph
172
+ */
173
+ hasEdge(source: NodeId, target: NodeId): boolean {
174
+ const sourceEdges = this.adjacencyList.get(source);
175
+ return sourceEdges ? sourceEdges.has(target) : false;
176
+ }
177
+
178
+ /**
179
+ * Get a node by ID
180
+ */
181
+ getNode(id: NodeId): Node | undefined {
182
+ return this.nodeMap.get(id);
183
+ }
184
+
185
+ /**
186
+ * Get an edge by source and target
187
+ */
188
+ getEdge(source: NodeId, target: NodeId): Edge | undefined {
189
+ const sourceEdges = this.adjacencyList.get(source);
190
+ return sourceEdges ? sourceEdges.get(target) : undefined;
191
+ }
192
+
193
+ /**
194
+ * Get the number of nodes in the graph
195
+ */
196
+ get nodeCount(): number {
197
+ return this.nodeMap.size;
198
+ }
199
+
200
+ /**
201
+ * Get the number of edges in the graph
202
+ */
203
+ get totalEdgeCount(): number {
204
+ return this.edgeCount;
205
+ }
206
+
207
+ /**
208
+ * Check if the graph is directed
209
+ */
210
+ get isDirected(): boolean {
211
+ return this.config.directed;
212
+ }
213
+
214
+ /**
215
+ * Get all nodes in the graph
216
+ */
217
+ nodes(): IterableIterator<Node> {
218
+ return this.nodeMap.values();
219
+ }
220
+
221
+ /**
222
+ * Get all edges in the graph
223
+ */
224
+ *edges(): IterableIterator<Edge> {
225
+ for (const [source, edges] of this.adjacencyList) {
226
+ for (const edge of edges.values()) {
227
+ // For undirected graphs, only yield each edge once
228
+ if (!this.config.directed && source > edge.target) {
229
+ continue;
230
+ }
231
+
232
+ yield edge;
233
+ }
234
+ }
235
+ }
236
+
237
+ /**
238
+ * Get neighbors of a node (outgoing edges)
239
+ */
240
+ neighbors(nodeId: NodeId): IterableIterator<NodeId> {
241
+ const edges = this.adjacencyList.get(nodeId);
242
+ return edges ? edges.keys() : new Map().keys();
243
+ }
244
+
245
+ /**
246
+ * Get incoming neighbors of a node (directed graphs only)
247
+ */
248
+ inNeighbors(nodeId: NodeId): IterableIterator<NodeId> {
249
+ if (!this.config.directed) {
250
+ return this.neighbors(nodeId);
251
+ }
252
+
253
+ const edges = this.incomingEdges.get(nodeId);
254
+ return edges ? edges.keys() : new Map().keys();
255
+ }
256
+
257
+ /**
258
+ * Get outgoing neighbors of a node
259
+ */
260
+ outNeighbors(nodeId: NodeId): IterableIterator<NodeId> {
261
+ return this.neighbors(nodeId);
262
+ }
263
+
264
+ /**
265
+ * Get the degree of a node
266
+ */
267
+ degree(nodeId: NodeId): number {
268
+ if (this.config.directed) {
269
+ return this.inDegree(nodeId) + this.outDegree(nodeId);
270
+ }
271
+
272
+ const edges = this.adjacencyList.get(nodeId);
273
+ return edges ? edges.size : 0;
274
+ }
275
+
276
+ /**
277
+ * Get the in-degree of a node
278
+ */
279
+ inDegree(nodeId: NodeId): number {
280
+ if (!this.config.directed) {
281
+ return this.degree(nodeId);
282
+ }
283
+
284
+ const edges = this.incomingEdges.get(nodeId);
285
+ return edges ? edges.size : 0;
286
+ }
287
+
288
+ /**
289
+ * Get the out-degree of a node
290
+ */
291
+ outDegree(nodeId: NodeId): number {
292
+ const edges = this.adjacencyList.get(nodeId);
293
+ return edges ? edges.size : 0;
294
+ }
295
+
296
+ /**
297
+ * Create a copy of the graph
298
+ */
299
+ clone(): Graph {
300
+ const cloned = new Graph(this.config);
301
+
302
+ // Copy nodes
303
+ for (const node of this.nodeMap.values()) {
304
+ cloned.addNode(node.id, node.data ? {... node.data} : undefined);
305
+ }
306
+
307
+ // Copy edges
308
+ for (const edge of this.edges()) {
309
+ cloned.addEdge(
310
+ edge.source,
311
+ edge.target,
312
+ edge.weight,
313
+ edge.data ? {... edge.data} : undefined,
314
+ );
315
+ }
316
+
317
+ return cloned;
318
+ }
319
+
320
+ /**
321
+ * Get graph configuration
322
+ */
323
+ getConfig(): GraphConfig {
324
+ return {... this.config};
325
+ }
326
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Data structures supporting graph algorithms
3
+ */
4
+
5
+ export {PriorityQueue} from "./priority-queue.js";
6
+ export {UnionFind} from "./union-find.js";