@graphty/algorithms 1.0.1 → 1.1.0
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.
- package/README.md +827 -60
- package/dist/algorithms.d.ts +2 -0
- package/dist/algorithms.d.ts.map +1 -0
- package/dist/algorithms.js +2 -0
- package/dist/algorithms.js.map +1 -0
- package/dist/src/algorithms/centrality/betweenness.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/betweenness.js +22 -7
- package/dist/src/algorithms/centrality/betweenness.js.map +1 -1
- package/dist/src/algorithms/centrality/closeness.js +7 -7
- package/dist/src/algorithms/centrality/closeness.js.map +1 -1
- package/dist/src/algorithms/centrality/degree.js +1 -1
- package/dist/src/algorithms/centrality/degree.js.map +1 -1
- package/dist/src/algorithms/centrality/eigenvector.d.ts +27 -0
- package/dist/src/algorithms/centrality/eigenvector.d.ts.map +1 -0
- package/dist/src/algorithms/centrality/eigenvector.js +113 -0
- package/dist/src/algorithms/centrality/eigenvector.js.map +1 -0
- package/dist/src/algorithms/centrality/hits.d.ts +35 -0
- package/dist/src/algorithms/centrality/hits.d.ts.map +1 -0
- package/dist/src/algorithms/centrality/hits.js +146 -0
- package/dist/src/algorithms/centrality/hits.js.map +1 -0
- package/dist/src/algorithms/centrality/index.d.ts +6 -0
- package/dist/src/algorithms/centrality/index.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/index.js +3 -0
- package/dist/src/algorithms/centrality/index.js.map +1 -1
- package/dist/src/algorithms/centrality/katz.d.ts +30 -0
- package/dist/src/algorithms/centrality/katz.d.ts.map +1 -0
- package/dist/src/algorithms/centrality/katz.js +85 -0
- package/dist/src/algorithms/centrality/katz.js.map +1 -0
- package/dist/src/algorithms/centrality/pagerank.js +3 -3
- package/dist/src/algorithms/centrality/pagerank.js.map +1 -1
- package/dist/src/algorithms/community/girvan-newman.d.ts +23 -0
- package/dist/src/algorithms/community/girvan-newman.d.ts.map +1 -0
- package/dist/src/algorithms/community/girvan-newman.js +297 -0
- package/dist/src/algorithms/community/girvan-newman.js.map +1 -0
- package/dist/src/algorithms/community/index.d.ts +12 -0
- package/dist/src/algorithms/community/index.d.ts.map +1 -0
- package/dist/src/algorithms/community/index.js +12 -0
- package/dist/src/algorithms/community/index.js.map +1 -0
- package/dist/src/algorithms/community/label-propagation.d.ts +42 -0
- package/dist/src/algorithms/community/label-propagation.d.ts.map +1 -0
- package/dist/src/algorithms/community/label-propagation.js +307 -0
- package/dist/src/algorithms/community/label-propagation.js.map +1 -0
- package/dist/src/algorithms/community/leiden.d.ts +33 -0
- package/dist/src/algorithms/community/leiden.d.ts.map +1 -0
- package/dist/src/algorithms/community/leiden.js +434 -0
- package/dist/src/algorithms/community/leiden.js.map +1 -0
- package/dist/src/algorithms/community/louvain.d.ts +19 -0
- package/dist/src/algorithms/community/louvain.d.ts.map +1 -0
- package/dist/src/algorithms/community/louvain.js +219 -0
- package/dist/src/algorithms/community/louvain.js.map +1 -0
- package/dist/src/algorithms/components/connected.js +7 -7
- package/dist/src/algorithms/components/connected.js.map +1 -1
- package/dist/src/algorithms/index.d.ts +7 -0
- package/dist/src/algorithms/index.d.ts.map +1 -1
- package/dist/src/algorithms/index.js +14 -0
- package/dist/src/algorithms/index.js.map +1 -1
- package/dist/src/algorithms/matching/bipartite.d.ts +37 -0
- package/dist/src/algorithms/matching/bipartite.d.ts.map +1 -0
- package/dist/src/algorithms/matching/bipartite.js +127 -0
- package/dist/src/algorithms/matching/bipartite.js.map +1 -0
- package/dist/src/algorithms/matching/index.d.ts +8 -0
- package/dist/src/algorithms/matching/index.d.ts.map +1 -0
- package/dist/src/algorithms/matching/index.js +6 -0
- package/dist/src/algorithms/matching/index.js.map +1 -0
- package/dist/src/algorithms/matching/isomorphism.d.ts +32 -0
- package/dist/src/algorithms/matching/isomorphism.d.ts.map +1 -0
- package/dist/src/algorithms/matching/isomorphism.js +291 -0
- package/dist/src/algorithms/matching/isomorphism.js.map +1 -0
- package/dist/src/algorithms/mst/index.d.ts +4 -0
- package/dist/src/algorithms/mst/index.d.ts.map +1 -0
- package/dist/src/algorithms/mst/index.js +3 -0
- package/dist/src/algorithms/mst/index.js.map +1 -0
- package/dist/src/algorithms/mst/kruskal.d.ts +9 -0
- package/dist/src/algorithms/mst/kruskal.d.ts.map +1 -0
- package/dist/src/algorithms/mst/kruskal.js +43 -0
- package/dist/src/algorithms/mst/kruskal.js.map +1 -0
- package/dist/src/algorithms/mst/prim.d.ts +5 -0
- package/dist/src/algorithms/mst/prim.d.ts.map +1 -0
- package/dist/src/algorithms/mst/prim.js +63 -0
- package/dist/src/algorithms/mst/prim.js.map +1 -0
- package/dist/src/algorithms/shortest-path/bellman-ford.js +2 -2
- package/dist/src/algorithms/shortest-path/bellman-ford.js.map +1 -1
- package/dist/src/algorithms/shortest-path/dijkstra.js +5 -5
- package/dist/src/algorithms/shortest-path/dijkstra.js.map +1 -1
- package/dist/src/algorithms/shortest-path/floyd-warshall.d.ts +14 -0
- package/dist/src/algorithms/shortest-path/floyd-warshall.d.ts.map +1 -0
- package/dist/src/algorithms/shortest-path/floyd-warshall.js +129 -0
- package/dist/src/algorithms/shortest-path/floyd-warshall.js.map +1 -0
- package/dist/src/algorithms/shortest-path/index.d.ts +2 -0
- package/dist/src/algorithms/shortest-path/index.d.ts.map +1 -1
- package/dist/src/algorithms/shortest-path/index.js +1 -0
- package/dist/src/algorithms/shortest-path/index.js.map +1 -1
- package/dist/src/algorithms/traversal/bfs.js +2 -2
- package/dist/src/algorithms/traversal/bfs.js.map +1 -1
- package/dist/src/algorithms/traversal/dfs.js +11 -11
- package/dist/src/algorithms/traversal/dfs.js.map +1 -1
- package/dist/src/clustering/hierarchical.d.ts +48 -0
- package/dist/src/clustering/hierarchical.d.ts.map +1 -0
- package/dist/src/clustering/hierarchical.js +439 -0
- package/dist/src/clustering/hierarchical.js.map +1 -0
- package/dist/src/clustering/index.d.ts +7 -0
- package/dist/src/clustering/index.d.ts.map +1 -0
- package/dist/src/clustering/index.js +5 -0
- package/dist/src/clustering/index.js.map +1 -0
- package/dist/src/clustering/k-core.d.ts +63 -0
- package/dist/src/clustering/k-core.d.ts.map +1 -0
- package/dist/src/clustering/k-core.js +377 -0
- package/dist/src/clustering/k-core.js.map +1 -0
- package/dist/src/clustering/mcl.d.ts +36 -0
- package/dist/src/clustering/mcl.d.ts.map +1 -0
- package/dist/src/clustering/mcl.js +343 -0
- package/dist/src/clustering/mcl.js.map +1 -0
- package/dist/src/clustering/spectral.d.ts +28 -0
- package/dist/src/clustering/spectral.d.ts.map +1 -0
- package/dist/src/clustering/spectral.js +447 -0
- package/dist/src/clustering/spectral.js.map +1 -0
- package/dist/src/core/graph.d.ts +9 -0
- package/dist/src/core/graph.d.ts.map +1 -1
- package/dist/src/core/graph.js +31 -11
- package/dist/src/core/graph.js.map +1 -1
- package/dist/src/data-structures/priority-queue.js +0 -2
- package/dist/src/data-structures/priority-queue.js.map +1 -1
- package/dist/src/data-structures/union-find.js +2 -5
- package/dist/src/data-structures/union-find.js.map +1 -1
- package/dist/src/flow/ford-fulkerson.d.ts +53 -0
- package/dist/src/flow/ford-fulkerson.d.ts.map +1 -0
- package/dist/src/flow/ford-fulkerson.js +355 -0
- package/dist/src/flow/ford-fulkerson.js.map +1 -0
- package/dist/src/flow/index.d.ts +3 -0
- package/dist/src/flow/index.d.ts.map +1 -0
- package/dist/src/flow/index.js +3 -0
- package/dist/src/flow/index.js.map +1 -0
- package/dist/src/flow/min-cut.d.ts +49 -0
- package/dist/src/flow/min-cut.d.ts.map +1 -0
- package/dist/src/flow/min-cut.js +383 -0
- package/dist/src/flow/min-cut.js.map +1 -0
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/link-prediction/adamic-adar.d.ts +56 -0
- package/dist/src/link-prediction/adamic-adar.d.ts.map +1 -0
- package/dist/src/link-prediction/adamic-adar.js +256 -0
- package/dist/src/link-prediction/adamic-adar.js.map +1 -0
- package/dist/src/link-prediction/common-neighbors.d.ts +50 -0
- package/dist/src/link-prediction/common-neighbors.d.ts.map +1 -0
- package/dist/src/link-prediction/common-neighbors.js +155 -0
- package/dist/src/link-prediction/common-neighbors.js.map +1 -0
- package/dist/src/link-prediction/index.d.ts +7 -0
- package/dist/src/link-prediction/index.d.ts.map +1 -0
- package/dist/src/link-prediction/index.js +6 -0
- package/dist/src/link-prediction/index.js.map +1 -0
- package/dist/src/pathfinding/astar.d.ts +50 -0
- package/dist/src/pathfinding/astar.d.ts.map +1 -0
- package/dist/src/pathfinding/astar.js +180 -0
- package/dist/src/pathfinding/astar.js.map +1 -0
- package/dist/src/pathfinding/index.d.ts +3 -0
- package/dist/src/pathfinding/index.d.ts.map +1 -0
- package/dist/src/pathfinding/index.js +3 -0
- package/dist/src/pathfinding/index.js.map +1 -0
- package/dist/src/pathfinding/utils.d.ts +18 -0
- package/dist/src/pathfinding/utils.d.ts.map +1 -0
- package/dist/src/pathfinding/utils.js +68 -0
- package/dist/src/pathfinding/utils.js.map +1 -0
- package/dist/src/types/index.d.ts +9 -0
- package/dist/src/types/index.d.ts.map +1 -1
- package/dist/src/utils/graphNode.d.ts +8 -0
- package/dist/src/utils/graphNode.d.ts.map +1 -0
- package/dist/src/utils/graphNode.js +2 -0
- package/dist/src/utils/graphNode.js.map +1 -0
- package/dist/src/utils/priorityQueue.d.ts +21 -0
- package/dist/src/utils/priorityQueue.d.ts.map +1 -0
- package/dist/src/utils/priorityQueue.js +101 -0
- package/dist/src/utils/priorityQueue.js.map +1 -0
- package/package.json +3 -1
- package/src/algorithms/centrality/betweenness.ts +25 -7
- package/src/algorithms/centrality/closeness.ts +7 -7
- package/src/algorithms/centrality/degree.ts +1 -1
- package/src/algorithms/centrality/eigenvector.ts +162 -0
- package/src/algorithms/centrality/hits.ts +210 -0
- package/src/algorithms/centrality/index.ts +6 -0
- package/src/algorithms/centrality/katz.ts +139 -0
- package/src/algorithms/centrality/pagerank.ts +3 -3
- package/src/algorithms/community/girvan-newman.ts +372 -0
- package/src/algorithms/community/index.ts +13 -0
- package/src/algorithms/community/label-propagation.ts +392 -0
- package/src/algorithms/community/leiden.ts +572 -0
- package/src/algorithms/community/louvain.ts +298 -0
- package/src/algorithms/components/connected.ts +7 -7
- package/src/algorithms/index.ts +21 -0
- package/src/algorithms/matching/bipartite.ts +179 -0
- package/src/algorithms/matching/index.ts +8 -0
- package/src/algorithms/matching/isomorphism.ts +392 -0
- package/src/algorithms/mst/index.ts +4 -0
- package/src/algorithms/mst/kruskal.ts +61 -0
- package/src/algorithms/mst/prim.ts +80 -0
- package/src/algorithms/shortest-path/bellman-ford.ts +2 -2
- package/src/algorithms/shortest-path/dijkstra.ts +5 -5
- package/src/algorithms/shortest-path/floyd-warshall.ts +170 -0
- package/src/algorithms/shortest-path/index.ts +2 -0
- package/src/algorithms/traversal/bfs.ts +2 -2
- package/src/algorithms/traversal/dfs.ts +11 -11
- package/src/clustering/hierarchical.ts +555 -0
- package/src/clustering/index.ts +6 -0
- package/src/clustering/k-core.ts +460 -0
- package/src/clustering/mcl.ts +454 -0
- package/src/clustering/spectral.ts +545 -0
- package/src/core/graph.ts +34 -6
- package/src/data-structures/union-find.ts +2 -2
- package/src/flow/ford-fulkerson.ts +459 -0
- package/src/flow/index.ts +2 -0
- package/src/flow/min-cut.ts +482 -0
- package/src/index.ts +2 -0
- package/src/link-prediction/adamic-adar.ts +353 -0
- package/src/link-prediction/common-neighbors.ts +252 -0
- package/src/link-prediction/index.ts +20 -0
- package/src/pathfinding/astar.ts +229 -0
- package/src/pathfinding/index.ts +2 -0
- package/src/pathfinding/utils.ts +87 -0
- package/src/types/index.ts +11 -0
- package/src/utils/graphNode.ts +7 -0
- package/src/utils/priorityQueue.ts +120 -0
- package/dist/test/browser/basic.test.d.ts +0 -2
- package/dist/test/browser/basic.test.d.ts.map +0 -1
- package/dist/test/browser/basic.test.js +0 -70
- package/dist/test/browser/basic.test.js.map +0 -1
- package/dist/test/unit/bellman-ford.test.d.ts +0 -2
- package/dist/test/unit/bellman-ford.test.d.ts.map +0 -1
- package/dist/test/unit/bellman-ford.test.js +0 -231
- package/dist/test/unit/bellman-ford.test.js.map +0 -1
- package/dist/test/unit/betweenness-centrality.test.d.ts +0 -2
- package/dist/test/unit/betweenness-centrality.test.d.ts.map +0 -1
- package/dist/test/unit/betweenness-centrality.test.js +0 -311
- package/dist/test/unit/betweenness-centrality.test.js.map +0 -1
- package/dist/test/unit/bfs.test.d.ts +0 -2
- package/dist/test/unit/bfs.test.d.ts.map +0 -1
- package/dist/test/unit/bfs.test.js +0 -277
- package/dist/test/unit/bfs.test.js.map +0 -1
- package/dist/test/unit/closeness.test.d.ts +0 -2
- package/dist/test/unit/closeness.test.d.ts.map +0 -1
- package/dist/test/unit/closeness.test.js +0 -266
- package/dist/test/unit/closeness.test.js.map +0 -1
- package/dist/test/unit/connected-components.test.d.ts +0 -2
- package/dist/test/unit/connected-components.test.d.ts.map +0 -1
- package/dist/test/unit/connected-components.test.js +0 -355
- package/dist/test/unit/connected-components.test.js.map +0 -1
- package/dist/test/unit/degree-centrality.test.d.ts +0 -2
- package/dist/test/unit/degree-centrality.test.d.ts.map +0 -1
- package/dist/test/unit/degree-centrality.test.js +0 -260
- package/dist/test/unit/degree-centrality.test.js.map +0 -1
- package/dist/test/unit/dfs.test.d.ts +0 -2
- package/dist/test/unit/dfs.test.d.ts.map +0 -1
- package/dist/test/unit/dfs.test.js +0 -350
- package/dist/test/unit/dfs.test.js.map +0 -1
- package/dist/test/unit/dijkstra.test.d.ts +0 -2
- package/dist/test/unit/dijkstra.test.d.ts.map +0 -1
- package/dist/test/unit/dijkstra.test.js +0 -311
- package/dist/test/unit/dijkstra.test.js.map +0 -1
- package/dist/test/unit/graph.test.d.ts +0 -2
- package/dist/test/unit/graph.test.d.ts.map +0 -1
- package/dist/test/unit/graph.test.js +0 -175
- package/dist/test/unit/graph.test.js.map +0 -1
- package/dist/test/unit/pagerank.test.d.ts +0 -2
- package/dist/test/unit/pagerank.test.d.ts.map +0 -1
- package/dist/test/unit/pagerank.test.js +0 -281
- package/dist/test/unit/pagerank.test.js.map +0 -1
- package/dist/test/unit/priority-queue.test.d.ts +0 -2
- package/dist/test/unit/priority-queue.test.d.ts.map +0 -1
- package/dist/test/unit/priority-queue.test.js +0 -189
- package/dist/test/unit/priority-queue.test.js.map +0 -1
- package/dist/test/unit/union-find.test.d.ts +0 -2
- package/dist/test/unit/union-find.test.d.ts.map +0 -1
- package/dist/test/unit/union-find.test.js +0 -245
- package/dist/test/unit/union-find.test.js.map +0 -1
|
@@ -0,0 +1,545 @@
|
|
|
1
|
+
import type {Graph} from "../core/graph.js";
|
|
2
|
+
import type {NodeId} from "../types/index.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Spectral Clustering implementation
|
|
6
|
+
*
|
|
7
|
+
* Uses eigenvalues and eigenvectors of the graph Laplacian matrix to perform clustering.
|
|
8
|
+
* Particularly effective for finding non-convex clusters and communities in graphs.
|
|
9
|
+
*
|
|
10
|
+
* Time complexity: O(V³) for eigendecomposition
|
|
11
|
+
* Space complexity: O(V²)
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export interface SpectralClusteringOptions {
|
|
15
|
+
k: number; // Number of clusters to find
|
|
16
|
+
laplacianType?: "unnormalized" | "normalized" | "randomWalk"; // Type of Laplacian
|
|
17
|
+
maxIterations?: number; // Max iterations for k-means (default: 100)
|
|
18
|
+
tolerance?: number; // Convergence tolerance (default: 1e-4)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface SpectralClusteringResult {
|
|
22
|
+
communities: NodeId[][];
|
|
23
|
+
clusterAssignments: Map<NodeId, number>;
|
|
24
|
+
eigenvalues?: number[];
|
|
25
|
+
eigenvectors?: number[][];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Perform spectral clustering on a graph
|
|
30
|
+
*/
|
|
31
|
+
export function spectralClustering(
|
|
32
|
+
graph: Graph,
|
|
33
|
+
options: SpectralClusteringOptions,
|
|
34
|
+
): SpectralClusteringResult {
|
|
35
|
+
const {
|
|
36
|
+
k,
|
|
37
|
+
laplacianType = "normalized",
|
|
38
|
+
maxIterations = 100,
|
|
39
|
+
} = options;
|
|
40
|
+
|
|
41
|
+
const nodes = Array.from(graph.nodes());
|
|
42
|
+
const nodeIds = nodes.map((node) => node.id);
|
|
43
|
+
const n = nodeIds.length;
|
|
44
|
+
|
|
45
|
+
if (k >= n) {
|
|
46
|
+
// Return each node as its own cluster
|
|
47
|
+
const communities: NodeId[][] = nodeIds.map((id) => [id]);
|
|
48
|
+
const clusterAssignments = new Map<NodeId, number>();
|
|
49
|
+
nodeIds.forEach((id, index) => clusterAssignments.set(id, index));
|
|
50
|
+
return {communities, clusterAssignments};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Build adjacency matrix
|
|
54
|
+
const adjacencyMatrix = buildAdjacencyMatrix(graph, nodeIds);
|
|
55
|
+
|
|
56
|
+
// Build Laplacian matrix
|
|
57
|
+
const laplacianMatrix = buildLaplacianMatrix(adjacencyMatrix, laplacianType);
|
|
58
|
+
|
|
59
|
+
// Find k smallest eigenvectors
|
|
60
|
+
const eigenResult = findSmallestEigenvectors(laplacianMatrix, k);
|
|
61
|
+
|
|
62
|
+
// Normalize the eigenvector matrix row-wise (for normalized spectral clustering)
|
|
63
|
+
if (laplacianType === "normalized") {
|
|
64
|
+
normalizeRows(eigenResult.eigenvectors);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Perform k-means clustering on the eigenvectors
|
|
68
|
+
const kmeans = kMeansClustering(eigenResult.eigenvectors, k, maxIterations);
|
|
69
|
+
|
|
70
|
+
// Build communities
|
|
71
|
+
const communities: NodeId[][] = Array.from({length: k}, () => []);
|
|
72
|
+
const clusterAssignments = new Map<NodeId, number>();
|
|
73
|
+
|
|
74
|
+
for (let i = 0; i < nodeIds.length; i++) {
|
|
75
|
+
const clusterId = kmeans.assignments[i] ?? 0;
|
|
76
|
+
const nodeId = nodeIds[i];
|
|
77
|
+
if (!nodeId) {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Ensure clusterId is valid
|
|
82
|
+
if (clusterId >= 0 && clusterId < k) {
|
|
83
|
+
const community = communities[clusterId];
|
|
84
|
+
if (community) {
|
|
85
|
+
community.push(nodeId);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
clusterAssignments.set(nodeId, clusterId);
|
|
89
|
+
} else {
|
|
90
|
+
// Assign to first cluster if invalid
|
|
91
|
+
const firstCommunity = communities[0];
|
|
92
|
+
if (firstCommunity) {
|
|
93
|
+
firstCommunity.push(nodeId);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
clusterAssignments.set(nodeId, 0);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Filter out empty communities
|
|
101
|
+
const nonEmptyCommunities = communities.filter((community) => community.length > 0);
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
communities: nonEmptyCommunities,
|
|
105
|
+
clusterAssignments,
|
|
106
|
+
eigenvalues: eigenResult.eigenvalues,
|
|
107
|
+
eigenvectors: eigenResult.eigenvectors,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Build adjacency matrix from graph
|
|
113
|
+
*/
|
|
114
|
+
function buildAdjacencyMatrix(graph: Graph, nodeIds: NodeId[]): number[][] {
|
|
115
|
+
const n = nodeIds.length;
|
|
116
|
+
const matrix: number[][] = Array.from({length: n}, () => Array(n).fill(0) as number[]);
|
|
117
|
+
const nodeToIndex = new Map<NodeId, number>();
|
|
118
|
+
|
|
119
|
+
nodeIds.forEach((id, index) => nodeToIndex.set(id, index));
|
|
120
|
+
|
|
121
|
+
for (let i = 0; i < n; i++) {
|
|
122
|
+
const nodeId = nodeIds[i];
|
|
123
|
+
if (!nodeId) {
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const neighbors = graph.neighbors(nodeId);
|
|
128
|
+
|
|
129
|
+
for (const neighbor of neighbors) {
|
|
130
|
+
const j = nodeToIndex.get(neighbor);
|
|
131
|
+
if (j !== undefined) {
|
|
132
|
+
const edge = graph.getEdge(nodeId, neighbor);
|
|
133
|
+
const weight = edge?.weight ?? 1;
|
|
134
|
+
const matrixRow = matrix[i];
|
|
135
|
+
if (matrixRow) {
|
|
136
|
+
matrixRow[j] = weight;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (!graph.isDirected) {
|
|
140
|
+
const matrixRowJ = matrix[j];
|
|
141
|
+
if (matrixRowJ) {
|
|
142
|
+
matrixRowJ[i] = weight;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return matrix;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Build Laplacian matrix from adjacency matrix
|
|
154
|
+
*/
|
|
155
|
+
function buildLaplacianMatrix(adjacency: number[][], type: string): number[][] {
|
|
156
|
+
const n = adjacency.length;
|
|
157
|
+
const laplacian: number[][] = Array.from({length: n}, () => Array(n).fill(0) as number[]);
|
|
158
|
+
|
|
159
|
+
// Calculate degree matrix
|
|
160
|
+
const degrees = Array(n).fill(0) as number[];
|
|
161
|
+
for (let i = 0; i < n; i++) {
|
|
162
|
+
for (let j = 0; j < n; j++) {
|
|
163
|
+
const adjacencyRow = adjacency[i];
|
|
164
|
+
const adjacencyVal = adjacencyRow ? adjacencyRow[j] : 0;
|
|
165
|
+
if (adjacencyVal !== undefined) {
|
|
166
|
+
const degreeVal = degrees[i];
|
|
167
|
+
if (degreeVal !== undefined) {
|
|
168
|
+
degrees[i] = degreeVal + adjacencyVal;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (type === "unnormalized") {
|
|
175
|
+
// L = D - A
|
|
176
|
+
for (let i = 0; i < n; i++) {
|
|
177
|
+
for (let j = 0; j < n; j++) {
|
|
178
|
+
if (i === j) {
|
|
179
|
+
const laplacianRow = laplacian[i];
|
|
180
|
+
const degreeVal = degrees[i];
|
|
181
|
+
if (laplacianRow && degreeVal !== undefined) {
|
|
182
|
+
laplacianRow[j] = degreeVal;
|
|
183
|
+
}
|
|
184
|
+
} else {
|
|
185
|
+
const adjacencyRow = adjacency[i];
|
|
186
|
+
const laplacianRow = laplacian[i];
|
|
187
|
+
if (adjacencyRow && laplacianRow) {
|
|
188
|
+
const adjacencyVal = adjacencyRow[j];
|
|
189
|
+
laplacianRow[j] = adjacencyVal !== undefined ? -adjacencyVal : 0;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
} else if (type === "normalized") {
|
|
195
|
+
// L_sym = D^(-1/2) * L * D^(-1/2)
|
|
196
|
+
for (let i = 0; i < n; i++) {
|
|
197
|
+
for (let j = 0; j < n; j++) {
|
|
198
|
+
if (i === j) {
|
|
199
|
+
const di = degrees[i];
|
|
200
|
+
const laplacianRow = laplacian[i];
|
|
201
|
+
if (laplacianRow) {
|
|
202
|
+
laplacianRow[j] = di !== undefined && di > 0 ? 1 : 0;
|
|
203
|
+
}
|
|
204
|
+
} else {
|
|
205
|
+
const adjacencyRow = adjacency[i];
|
|
206
|
+
if (!adjacencyRow) {
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const adjacencyVal = adjacencyRow[j];
|
|
211
|
+
const di = degrees[i];
|
|
212
|
+
const dj = degrees[j];
|
|
213
|
+
if (adjacencyVal !== undefined && adjacencyVal > 0 && di !== undefined && dj !== undefined && di > 0 && dj > 0) {
|
|
214
|
+
const laplacianRow = laplacian[i];
|
|
215
|
+
if (laplacianRow) {
|
|
216
|
+
laplacianRow[j] = -adjacencyVal / Math.sqrt(di * dj);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
} else if (type === "randomWalk") {
|
|
223
|
+
// L_rw = D^(-1) * L
|
|
224
|
+
for (let i = 0; i < n; i++) {
|
|
225
|
+
for (let j = 0; j < n; j++) {
|
|
226
|
+
if (i === j) {
|
|
227
|
+
const di = degrees[i];
|
|
228
|
+
const laplacianRow = laplacian[i];
|
|
229
|
+
if (laplacianRow) {
|
|
230
|
+
laplacianRow[j] = di !== undefined && di > 0 ? 1 : 0;
|
|
231
|
+
}
|
|
232
|
+
} else {
|
|
233
|
+
const adjacencyRow = adjacency[i];
|
|
234
|
+
if (!adjacencyRow) {
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const adjacencyVal = adjacencyRow[j];
|
|
239
|
+
const di = degrees[i];
|
|
240
|
+
if (adjacencyVal !== undefined && adjacencyVal > 0 && di !== undefined && di > 0) {
|
|
241
|
+
const laplacianRow = laplacian[i];
|
|
242
|
+
if (laplacianRow) {
|
|
243
|
+
laplacianRow[j] = -adjacencyVal / di;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return laplacian;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Find k smallest eigenvectors using simplified eigendecomposition
|
|
256
|
+
* This is a simplified implementation - in practice, you'd use LAPACK or similar
|
|
257
|
+
*/
|
|
258
|
+
function findSmallestEigenvectors(matrix: number[][], k: number): {
|
|
259
|
+
eigenvalues: number[];
|
|
260
|
+
eigenvectors: number[][];
|
|
261
|
+
} {
|
|
262
|
+
const n = matrix.length;
|
|
263
|
+
|
|
264
|
+
if (n === 0 || k === 0) {
|
|
265
|
+
return {eigenvalues: [], eigenvectors: []};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// For small matrices or when k >= n, use a simplified approach
|
|
269
|
+
if (n <= 3 || k >= n) {
|
|
270
|
+
// Create identity-like eigenvectors for simplicity
|
|
271
|
+
const eigenvectors: number[][] = [];
|
|
272
|
+
const eigenvalues: number[] = [];
|
|
273
|
+
|
|
274
|
+
for (let i = 0; i < Math.min(k, n); i++) {
|
|
275
|
+
const vector = Array(n).fill(0) as number[];
|
|
276
|
+
vector[i] = 1;
|
|
277
|
+
eigenvectors.push(vector);
|
|
278
|
+
eigenvalues.push(i);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return {eigenvalues, eigenvectors};
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// For larger matrices, use power iteration to find dominant eigenvectors
|
|
285
|
+
// This is still simplified but better than random
|
|
286
|
+
const eigenvectors: number[][] = [];
|
|
287
|
+
const eigenvalues: number[] = [];
|
|
288
|
+
const maxIterations = 50;
|
|
289
|
+
|
|
290
|
+
for (let eigIdx = 0; eigIdx < k; eigIdx++) {
|
|
291
|
+
// Initialize random vector
|
|
292
|
+
let vector = Array(n).fill(0).map(() => Math.random() - 0.5);
|
|
293
|
+
|
|
294
|
+
// Orthogonalize against previous eigenvectors
|
|
295
|
+
for (let j = 0; j < eigIdx; j++) {
|
|
296
|
+
const ejVector = eigenvectors[j];
|
|
297
|
+
if (!ejVector) {
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const dot = vector.reduce((sum, val, idx) => sum + (val * (ejVector[idx] ?? 0)), 0);
|
|
302
|
+
vector = vector.map((val, idx) => val - (dot * (ejVector[idx] ?? 0)));
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Power iteration
|
|
306
|
+
for (let iter = 0; iter < maxIterations; iter++) {
|
|
307
|
+
// Multiply by matrix
|
|
308
|
+
const newVector = Array(n).fill(0) as number[];
|
|
309
|
+
for (let i = 0; i < n; i++) {
|
|
310
|
+
for (let j = 0; j < n; j++) {
|
|
311
|
+
const matrixRow = matrix[i];
|
|
312
|
+
const vecVal = vector[j];
|
|
313
|
+
const matrixVal = matrixRow?.[j];
|
|
314
|
+
if (matrixVal !== undefined && vecVal !== undefined) {
|
|
315
|
+
const nvVal = newVector[i];
|
|
316
|
+
if (nvVal !== undefined) {
|
|
317
|
+
newVector[i] = nvVal + (matrixVal * vecVal);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Orthogonalize against previous eigenvectors
|
|
324
|
+
for (let j = 0; j < eigIdx; j++) {
|
|
325
|
+
const ejVector = eigenvectors[j];
|
|
326
|
+
if (!ejVector) {
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const dot = newVector.reduce((sum, val, idx) => sum + (val * (ejVector[idx] ?? 0)), 0);
|
|
331
|
+
for (let i = 0; i < n; i++) {
|
|
332
|
+
const ejVal = ejVector[i];
|
|
333
|
+
if (ejVal !== undefined) {
|
|
334
|
+
const nvVal = newVector[i];
|
|
335
|
+
if (nvVal !== undefined) {
|
|
336
|
+
newVector[i] = nvVal - (dot * ejVal);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Normalize
|
|
343
|
+
const norm = Math.sqrt(newVector.reduce((sum, val) => sum + ((val * val)), 0));
|
|
344
|
+
if (norm > 1e-10) {
|
|
345
|
+
vector = newVector.map((val) => val / norm);
|
|
346
|
+
} else {
|
|
347
|
+
break;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Calculate eigenvalue (Rayleigh quotient)
|
|
352
|
+
let eigenvalue = 0;
|
|
353
|
+
const Av = Array(n).fill(0) as number[];
|
|
354
|
+
for (let i = 0; i < n; i++) {
|
|
355
|
+
for (let j = 0; j < n; j++) {
|
|
356
|
+
const matrixRow = matrix[i];
|
|
357
|
+
const vecVal = vector[j];
|
|
358
|
+
const matrixVal = matrixRow?.[j];
|
|
359
|
+
if (matrixVal !== undefined && vecVal !== undefined) {
|
|
360
|
+
const avVal = Av[i];
|
|
361
|
+
if (avVal !== undefined) {
|
|
362
|
+
Av[i] = avVal + (matrixVal * vecVal);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
eigenvalue = vector.reduce((sum, val, idx) => {
|
|
368
|
+
const avVal = Av[idx];
|
|
369
|
+
return sum + (val * (avVal ?? 0));
|
|
370
|
+
}, 0);
|
|
371
|
+
|
|
372
|
+
eigenvectors.push(vector);
|
|
373
|
+
eigenvalues.push(eigenvalue);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
return {eigenvalues, eigenvectors};
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Normalize matrix rows to unit length
|
|
381
|
+
*/
|
|
382
|
+
function normalizeRows(matrix: number[][]): void {
|
|
383
|
+
for (const row of matrix) {
|
|
384
|
+
const norm = Math.sqrt(row.reduce((sum, val) => sum + (val * val), 0));
|
|
385
|
+
if (norm > 0) {
|
|
386
|
+
for (let j = 0; j < row.length; j++) {
|
|
387
|
+
const val = row[j];
|
|
388
|
+
if (val !== undefined) {
|
|
389
|
+
row[j] = val / norm;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* K-means clustering algorithm
|
|
398
|
+
*/
|
|
399
|
+
function kMeansClustering(
|
|
400
|
+
data: number[][],
|
|
401
|
+
k: number,
|
|
402
|
+
maxIterations: number,
|
|
403
|
+
): {assignments: number[], centroids: number[][]} {
|
|
404
|
+
const n = data.length;
|
|
405
|
+
const d = data[0]?.length ?? 0;
|
|
406
|
+
|
|
407
|
+
// Handle edge cases
|
|
408
|
+
if (n === 0 || k === 0) {
|
|
409
|
+
return {assignments: [], centroids: []};
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if (k >= n) {
|
|
413
|
+
// Each point is its own cluster
|
|
414
|
+
return {
|
|
415
|
+
assignments: Array.from({length: n}, (_, i) => i),
|
|
416
|
+
centroids: data.slice(0, n),
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Initialize centroids by selecting random data points
|
|
421
|
+
const centroids: number[][] = [];
|
|
422
|
+
const selectedIndices = new Set<number>();
|
|
423
|
+
|
|
424
|
+
while (centroids.length < k && selectedIndices.size < n) {
|
|
425
|
+
const idx = Math.floor(Math.random() * n);
|
|
426
|
+
if (!selectedIndices.has(idx) && data[idx]) {
|
|
427
|
+
selectedIndices.add(idx);
|
|
428
|
+
centroids.push([... data[idx]]);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// Fill remaining centroids with random values if needed
|
|
433
|
+
while (centroids.length < k) {
|
|
434
|
+
const centroid = Array(d).fill(0) as number[];
|
|
435
|
+
for (let j = 0; j < d; j++) {
|
|
436
|
+
centroid[j] = Math.random() - 0.5;
|
|
437
|
+
}
|
|
438
|
+
centroids.push(centroid);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
const assignments = Array(n).fill(0) as number[];
|
|
442
|
+
let oldAssignments = Array(n).fill(-1) as number[];
|
|
443
|
+
|
|
444
|
+
for (let iteration = 0; iteration < maxIterations; iteration++) {
|
|
445
|
+
// Assign points to closest centroids
|
|
446
|
+
for (let i = 0; i < n; i++) {
|
|
447
|
+
let minDistance = Number.POSITIVE_INFINITY;
|
|
448
|
+
let bestCluster = 0;
|
|
449
|
+
|
|
450
|
+
for (let j = 0; j < k; j++) {
|
|
451
|
+
const dataPoint = data[i];
|
|
452
|
+
const centroid = centroids[j];
|
|
453
|
+
if (!dataPoint || !centroid) {
|
|
454
|
+
continue;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
const distance = euclideanDistance(dataPoint, centroid);
|
|
458
|
+
if (distance < minDistance) {
|
|
459
|
+
minDistance = distance;
|
|
460
|
+
bestCluster = j;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
assignments[i] = bestCluster;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// Check for convergence
|
|
468
|
+
let changed = false;
|
|
469
|
+
for (let i = 0; i < n; i++) {
|
|
470
|
+
if (assignments[i] !== oldAssignments[i]) {
|
|
471
|
+
changed = true;
|
|
472
|
+
break;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
if (!changed) {
|
|
477
|
+
break;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
oldAssignments = [... assignments];
|
|
481
|
+
|
|
482
|
+
// Update centroids
|
|
483
|
+
const counts = Array(k).fill(0) as number[];
|
|
484
|
+
const sums: number[][] = Array.from({length: k}, () => Array(d).fill(0) as number[]);
|
|
485
|
+
|
|
486
|
+
for (let i = 0; i < n; i++) {
|
|
487
|
+
const cluster = assignments[i] ?? 0;
|
|
488
|
+
const dataPoint = data[i];
|
|
489
|
+
if (!dataPoint) {
|
|
490
|
+
continue;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
const sumsCluster = sums[cluster];
|
|
494
|
+
if (sumsCluster !== undefined) {
|
|
495
|
+
const countVal = counts[cluster];
|
|
496
|
+
if (countVal !== undefined) {
|
|
497
|
+
counts[cluster] = countVal + 1;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
for (let j = 0; j < d; j++) {
|
|
501
|
+
const dpVal = dataPoint[j];
|
|
502
|
+
if (dpVal !== undefined) {
|
|
503
|
+
const sumVal = sumsCluster[j];
|
|
504
|
+
if (sumVal !== undefined) {
|
|
505
|
+
sumsCluster[j] = sumVal + dpVal;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
for (let i = 0; i < k; i++) {
|
|
513
|
+
const countVal = counts[i];
|
|
514
|
+
if (countVal !== undefined && countVal > 0) {
|
|
515
|
+
const sumsRow = sums[i];
|
|
516
|
+
const centroidsRow = centroids[i];
|
|
517
|
+
if (sumsRow !== undefined && centroidsRow !== undefined) {
|
|
518
|
+
for (let j = 0; j < d; j++) {
|
|
519
|
+
const sumVal = sumsRow[j];
|
|
520
|
+
if (sumVal !== undefined) {
|
|
521
|
+
const countVal = counts[i];
|
|
522
|
+
if (countVal !== undefined) {
|
|
523
|
+
centroidsRow[j] = sumVal / countVal;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
return {assignments, centroids};
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* Calculate Euclidean distance between two vectors
|
|
537
|
+
*/
|
|
538
|
+
function euclideanDistance(a: number[], b: number[]): number {
|
|
539
|
+
let sum = 0;
|
|
540
|
+
for (let i = 0; i < a.length; i++) {
|
|
541
|
+
const diff = (a[i] ?? 0) - (b[i] ?? 0);
|
|
542
|
+
sum += diff * diff;
|
|
543
|
+
}
|
|
544
|
+
return Math.sqrt(sum);
|
|
545
|
+
}
|
package/src/core/graph.ts
CHANGED
|
@@ -51,7 +51,7 @@ export class Graph {
|
|
|
51
51
|
// Remove all edges connected to this node
|
|
52
52
|
const outgoingEdges = this.adjacencyList.get(id);
|
|
53
53
|
if (outgoingEdges) {
|
|
54
|
-
for (const targetId of outgoingEdges.keys()) {
|
|
54
|
+
for (const targetId of Array.from(outgoingEdges.keys())) {
|
|
55
55
|
this.removeEdge(id, targetId);
|
|
56
56
|
}
|
|
57
57
|
}
|
|
@@ -59,13 +59,13 @@ export class Graph {
|
|
|
59
59
|
if (this.config.directed) {
|
|
60
60
|
const incomingEdges = this.incomingEdges.get(id);
|
|
61
61
|
if (incomingEdges) {
|
|
62
|
-
for (const sourceId of incomingEdges.keys()) {
|
|
62
|
+
for (const sourceId of Array.from(incomingEdges.keys())) {
|
|
63
63
|
this.removeEdge(sourceId, id);
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
} else {
|
|
67
67
|
// For undirected graphs, also remove edges where this node is the target
|
|
68
|
-
for (const [nodeId, edges] of this.adjacencyList) {
|
|
68
|
+
for (const [nodeId, edges] of Array.from(this.adjacencyList)) {
|
|
69
69
|
if (edges.has(id)) {
|
|
70
70
|
this.removeEdge(nodeId, id);
|
|
71
71
|
}
|
|
@@ -222,7 +222,7 @@ export class Graph {
|
|
|
222
222
|
* Get all edges in the graph
|
|
223
223
|
*/
|
|
224
224
|
*edges(): IterableIterator<Edge> {
|
|
225
|
-
for (const [source, edges] of this.adjacencyList) {
|
|
225
|
+
for (const [source, edges] of Array.from(this.adjacencyList)) {
|
|
226
226
|
for (const edge of edges.values()) {
|
|
227
227
|
// For undirected graphs, only yield each edge once
|
|
228
228
|
if (!this.config.directed && source > edge.target) {
|
|
@@ -300,12 +300,12 @@ export class Graph {
|
|
|
300
300
|
const cloned = new Graph(this.config);
|
|
301
301
|
|
|
302
302
|
// Copy nodes
|
|
303
|
-
for (const node of this.nodeMap.values()) {
|
|
303
|
+
for (const node of Array.from(this.nodeMap.values())) {
|
|
304
304
|
cloned.addNode(node.id, node.data ? {... node.data} : undefined);
|
|
305
305
|
}
|
|
306
306
|
|
|
307
307
|
// Copy edges
|
|
308
|
-
for (const edge of this.edges()) {
|
|
308
|
+
for (const edge of Array.from(this.edges())) {
|
|
309
309
|
cloned.addEdge(
|
|
310
310
|
edge.source,
|
|
311
311
|
edge.target,
|
|
@@ -323,4 +323,32 @@ export class Graph {
|
|
|
323
323
|
getConfig(): GraphConfig {
|
|
324
324
|
return {... this.config};
|
|
325
325
|
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Clear all nodes and edges from the graph
|
|
329
|
+
*/
|
|
330
|
+
clear(): void {
|
|
331
|
+
this.nodeMap.clear();
|
|
332
|
+
this.adjacencyList.clear();
|
|
333
|
+
this.incomingEdges.clear();
|
|
334
|
+
this.edgeCount = 0;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Get the number of unique edges in the graph
|
|
339
|
+
* For undirected graphs, each edge is counted once
|
|
340
|
+
*/
|
|
341
|
+
get uniqueEdgeCount(): number {
|
|
342
|
+
if (this.config.directed) {
|
|
343
|
+
return this.edgeCount;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// For undirected graphs, we need to count each edge only once
|
|
347
|
+
let count = 0;
|
|
348
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
349
|
+
for (const _edge of Array.from(this.edges())) {
|
|
350
|
+
count++;
|
|
351
|
+
}
|
|
352
|
+
return count;
|
|
353
|
+
}
|
|
326
354
|
}
|
|
@@ -103,7 +103,7 @@ export class UnionFind {
|
|
|
103
103
|
const root = this.find(element);
|
|
104
104
|
const component: NodeId[] = [];
|
|
105
105
|
|
|
106
|
-
for (const [node] of this.parent) {
|
|
106
|
+
for (const [node] of Array.from(this.parent)) {
|
|
107
107
|
if (this.find(node) === root) {
|
|
108
108
|
component.push(node);
|
|
109
109
|
}
|
|
@@ -118,7 +118,7 @@ export class UnionFind {
|
|
|
118
118
|
getAllComponents(): NodeId[][] {
|
|
119
119
|
const componentMap = new Map<NodeId, NodeId[]>();
|
|
120
120
|
|
|
121
|
-
for (const [node] of this.parent) {
|
|
121
|
+
for (const [node] of Array.from(this.parent)) {
|
|
122
122
|
const root = this.find(node);
|
|
123
123
|
|
|
124
124
|
if (!componentMap.has(root)) {
|