//@ts-check
const MathHelper = require('./MathHelper')
const Vector2 = require('./Vector2')
const Vertex = require('./Vertex')
const Edge = require('./Edge')
const Ring = require('./Ring')
const Atom = require('./Atom')
/**
* A class representing the molecular graph.
*
* @property {Vertex[]} vertices The vertices of the graph.
* @property {Edge[]} edges The edges of this graph.
* @property {Number[]} atomIdxToVertexId A map mapping atom indices to vertex ids.
* @property {Object} vertexIdsToEdgeId A map mapping vertex ids to the edge between the two vertices. The key is defined as vertexAId + '_' + vertexBId.
* @property {Boolean} isometric A boolean indicating whether or not the SMILES associated with this graph is isometric.
*/
class Graph {
/**
* The constructor of the class Graph.
*
* @param {Object} parseTree A SMILES parse tree.
* @param {Boolean} [isomeric=false] A boolean specifying whether or not the SMILES is isomeric.
*/
constructor(parseTree, isomeric = false) {
this.vertices = Array();
this.edges = Array();
this.atomIdxToVertexId = Array();
this.vertexIdsToEdgeId = {};
this.isomeric = isomeric;
// Used to assign indices to the heavy atoms.
this._atomIdx = 0;
// Used for the bridge detection algorithm
this._time = 0;
this._init(parseTree);
}
/**
* PRIVATE FUNCTION. Initializing the graph from the parse tree.
*
* @param {Object} node The current node in the parse tree.
* @param {?Number} parentVertexId=null The id of the previous vertex.
* @param {Boolean} isBranch=false Whether or not the bond leading to this vertex is a branch bond. Branches are represented by parentheses in smiles (e.g. CC(O)C).
*/
_init(node, order = 0, parentVertexId = null, isBranch = false) {
// Create a new vertex object
const element = node.atom.element ? node.atom.element : node.atom;
let atom = new Atom(element, node.bond);
if (element !== 'H' || (!node.hasNext && parentVertexId === null)) {
atom.idx = this._atomIdx;
this._atomIdx++;
}
atom.branchBond = node.branchBond;
atom.ringbonds = node.ringbonds;
atom.bracket = node.atom.element ? node.atom : null;
atom.class = node.atom.class
let vertex = new Vertex(atom);
let parentVertex = this.vertices[parentVertexId];
this.addVertex(vertex);
if (atom.idx !== null) {
this.atomIdxToVertexId.push(vertex.id);
}
// Add the id of this node to the parent as child
if (parentVertexId !== null) {
vertex.setParentVertexId(parentVertexId);
vertex.value.addNeighbouringElement(parentVertex.value.element);
parentVertex.addChild(vertex.id);
parentVertex.value.addNeighbouringElement(atom.element);
// In addition create a spanningTreeChildren property, which later will
// not contain the children added through ringbonds
parentVertex.spanningTreeChildren.push(vertex.id);
// Add edge between this node and its parent
let edge = new Edge(parentVertexId, vertex.id, 1);
let vertexId = null;
if (isBranch) {
edge.setBondType(vertex.value.branchBond || '-');
vertexId = vertex.id;
edge.setBondType(vertex.value.branchBond || '-');
vertexId = vertex.id;
} else {
edge.setBondType(parentVertex.value.bondType || '-');
vertexId = parentVertex.id;
}
let edgeId = this.addEdge(edge);
}
let offset = node.ringbondCount + 1;
if (atom.bracket) {
offset += atom.bracket.hcount;
}
let stereoHydrogens = 0;
if (atom.bracket && atom.bracket.chirality) {
atom.isStereoCenter = true;
stereoHydrogens = atom.bracket.hcount;
for (var i = 0; i < stereoHydrogens; i++) {
this._init({
atom: 'H',
isBracket: 'false',
branches: Array(),
branchCount: 0,
ringbonds: Array(),
ringbondCount: false,
next: null,
hasNext: false,
bond: '-'
}, i, vertex.id, true);
}
}
for (var i = 0; i < node.branchCount; i++) {
this._init(node.branches[i], i + offset, vertex.id, true);
}
if (node.hasNext) {
this._init(node.next, node.branchCount + offset, vertex.id);
}
}
/**
* Clears all the elements in this graph (edges and vertices).
*/
clear() {
this.vertices = Array();
this.edges = Array();
this.vertexIdsToEdgeId = {};
}
/**
* Add a vertex to the graph.
*
* @param {Vertex} vertex A new vertex.
* @returns {Number} The vertex id of the new vertex.
*/
addVertex(vertex) {
vertex.id = this.vertices.length;
this.vertices.push(vertex);
return vertex.id;
}
/**
* Add an edge to the graph.
*
* @param {Edge} edge A new edge.
* @returns {Number} The edge id of the new edge.
*/
addEdge(edge) {
let source = this.vertices[edge.sourceId];
let target = this.vertices[edge.targetId];
edge.id = this.edges.length;
this.edges.push(edge);
this.vertexIdsToEdgeId[edge.sourceId + '_' + edge.targetId] = edge.id;
this.vertexIdsToEdgeId[edge.targetId + '_' + edge.sourceId] = edge.id;
edge.isPartOfAromaticRing = source.value.isPartOfAromaticRing && target.value.isPartOfAromaticRing;
source.value.bondCount += edge.weight;
target.value.bondCount += edge.weight;
source.edges.push(edge.id);
target.edges.push(edge.id);
return edge.id;
}
/**
* Returns the edge between two given vertices.
*
* @param {Number} vertexIdA A vertex id.
* @param {Number} vertexIdB A vertex id.
* @returns {(Edge|null)} The edge or, if no edge can be found, null.
*/
getEdge(vertexIdA, vertexIdB) {
let edgeId = this.vertexIdsToEdgeId[vertexIdA + '_' + vertexIdB];
return edgeId === undefined ? null : this.edges[edgeId];
}
/**
* Returns the ids of edges connected to a vertex.
*
* @param {Number} vertexId A vertex id.
* @returns {Number[]} An array containing the ids of edges connected to the vertex.
*/
getEdges(vertexId) {
let edgeIds = Array();
let vertex = this.vertices[vertexId];
for (var i = 0; i < vertex.neighbours.length; i++) {
edgeIds.push(this.vertexIdsToEdgeId[vertexId + '_' + vertex.neighbours[i]]);
}
return edgeIds;
}
/**
* Check whether or not two vertices are connected by an edge.
*
* @param {Number} vertexIdA A vertex id.
* @param {Number} vertexIdB A vertex id.
* @returns {Boolean} A boolean indicating whether or not two vertices are connected by an edge.
*/
hasEdge(vertexIdA, vertexIdB) {
return this.vertexIdsToEdgeId[vertexIdA + '_' + vertexIdB] !== undefined
}
/**
* Returns an array containing the vertex ids of this graph.
*
* @returns {Number[]} An array containing all vertex ids of this graph.
*/
getVertexList() {
let arr = [this.vertices.length];
for (var i = 0; i < this.vertices.length; i++) {
arr[i] = this.vertices[i].id;
}
return arr;
}
/**
* Returns an array containing source, target arrays of this graphs edges.
*
* @returns {Array[]} An array containing source, target arrays of this graphs edges. Example: [ [ 2, 5 ], [ 6, 9 ] ].
*/
getEdgeList() {
let arr = Array(this.edges.length);
for (var i = 0; i < this.edges.length; i++) {
arr[i] = [this.edges[i].sourceId, this.edges[i].targetId];
}
return arr;
}
/**
* Get the adjacency matrix of the graph.
*
* @returns {Array[]} The adjancency matrix of the molecular graph.
*/
getAdjacencyMatrix() {
let length = this.vertices.length;
let adjacencyMatrix = Array(length);
for (var i = 0; i < length; i++) {
adjacencyMatrix[i] = new Array(length);
adjacencyMatrix[i].fill(0);
}
for (var i = 0; i < this.edges.length; i++) {
let edge = this.edges[i];
adjacencyMatrix[edge.sourceId][edge.targetId] = 1;
adjacencyMatrix[edge.targetId][edge.sourceId] = 1;
}
return adjacencyMatrix;
}
/**
* Get the adjacency matrix of the graph with all bridges removed (thus the components). Thus the remaining vertices are all part of ring systems.
*
* @returns {Array[]} The adjancency matrix of the molecular graph with all bridges removed.
*/
getComponentsAdjacencyMatrix() {
let length = this.vertices.length;
let adjacencyMatrix = Array(length);
let bridges = this.getBridges();
for (var i = 0; i < length; i++) {
adjacencyMatrix[i] = new Array(length);
adjacencyMatrix[i].fill(0);
}
for (var i = 0; i < this.edges.length; i++) {
let edge = this.edges[i];
adjacencyMatrix[edge.sourceId][edge.targetId] = 1;
adjacencyMatrix[edge.targetId][edge.sourceId] = 1;
}
for (var i = 0; i < bridges.length; i++) {
adjacencyMatrix[bridges[i][0]][bridges[i][1]] = 0;
adjacencyMatrix[bridges[i][1]][bridges[i][0]] = 0;
}
return adjacencyMatrix;
}
/**
* Get the adjacency matrix of a subgraph.
*
* @param {Number[]} vertexIds An array containing the vertex ids contained within the subgraph.
* @returns {Array[]} The adjancency matrix of the subgraph.
*/
getSubgraphAdjacencyMatrix(vertexIds) {
let length = vertexIds.length;
let adjacencyMatrix = Array(length);
for (var i = 0; i < length; i++) {
adjacencyMatrix[i] = new Array(length);
adjacencyMatrix[i].fill(0);
for (var j = 0; j < length; j++) {
if (i === j) {
continue;
}
if (this.hasEdge(vertexIds[i], vertexIds[j])) {
adjacencyMatrix[i][j] = 1;
}
}
}
return adjacencyMatrix;
}
/**
* Get the distance matrix of the graph.
*
* @returns {Array[]} The distance matrix of the graph.
*/
getDistanceMatrix() {
let length = this.vertices.length;
let adja = this.getAdjacencyMatrix();
let dist = Array(length);
for (var i = 0; i < length; i++) {
dist[i] = Array(length);
dist[i].fill(Infinity);
}
for (var i = 0; i < length; i++) {
for (var j = 0; j < length; j++) {
if (adja[i][j] === 1) {
dist[i][j] = 1;
}
}
}
for (var k = 0; k < length; k++) {
for (var i = 0; i < length; i++) {
for (var j = 0; j < length; j++) {
if (dist[i][j] > dist[i][k] + dist[k][j]) {
dist[i][j] = dist[i][k] + dist[k][j]
}
}
}
}
return dist;
}
/**
* Get the distance matrix of a subgraph.
*
* @param {Number[]} vertexIds An array containing the vertex ids contained within the subgraph.
* @returns {Array[]} The distance matrix of the subgraph.
*/
getSubgraphDistanceMatrix(vertexIds) {
let length = vertexIds.length;
let adja = this.getSubgraphAdjacencyMatrix(vertexIds);
let dist = Array(length);
for (var i = 0; i < length; i++) {
dist[i] = Array(length);
dist[i].fill(Infinity);
}
for (var i = 0; i < length; i++) {
for (var j = 0; j < length; j++) {
if (adja[i][j] === 1) {
dist[i][j] = 1;
}
}
}
for (var k = 0; k < length; k++) {
for (var i = 0; i < length; i++) {
for (var j = 0; j < length; j++) {
if (dist[i][j] > dist[i][k] + dist[k][j]) {
dist[i][j] = dist[i][k] + dist[k][j]
}
}
}
}
return dist;
}
/**
* Get the adjacency list of the graph.
*
* @returns {Array[]} The adjancency list of the graph.
*/
getAdjacencyList() {
let length = this.vertices.length;
let adjacencyList = Array(length);
for (var i = 0; i < length; i++) {
adjacencyList[i] = [];
for (var j = 0; j < length; j++) {
if (i === j) {
continue;
}
if (this.hasEdge(this.vertices[i].id, this.vertices[j].id)) {
adjacencyList[i].push(j);
}
}
}
return adjacencyList;
}
/**
* Get the adjacency list of a subgraph.
*
* @param {Number[]} vertexIds An array containing the vertex ids contained within the subgraph.
* @returns {Array[]} The adjancency list of the subgraph.
*/
getSubgraphAdjacencyList(vertexIds) {
let length = vertexIds.length;
let adjacencyList = Array(length);
for (var i = 0; i < length; i++) {
adjacencyList[i] = Array();
for (var j = 0; j < length; j++) {
if (i === j) {
continue;
}
if (this.hasEdge(vertexIds[i], vertexIds[j])) {
adjacencyList[i].push(j);
}
}
}
return adjacencyList;
}
/**
* Returns an array containing the edge ids of bridges. A bridge splits the graph into multiple components when removed.
*
* @returns {Number[]} An array containing the edge ids of the bridges.
*/
getBridges() {
let length = this.vertices.length;
let visited = new Array(length);
let disc = new Array(length);
let low = new Array(length);
let parent = new Array(length);
let adj = this.getAdjacencyList();
let outBridges = Array();
visited.fill(false);
parent.fill(null);
this._time = 0;
for (var i = 0; i < length; i++) {
if (!visited[i]) {
this._bridgeDfs(i, visited, disc, low, parent, adj, outBridges);
}
}
return outBridges;
}
/**
* Traverses the graph in breadth-first order.
*
* @param {Number} startVertexId The id of the starting vertex.
* @param {Function} callback The callback function to be called on every vertex.
*/
traverseBF(startVertexId, callback) {
let length = this.vertices.length;
let visited = new Array(length);
visited.fill(false);
var queue = [startVertexId];
while (queue.length > 0) {
// JavaScripts shift() is O(n) ... bad JavaScript, bad!
let u = queue.shift();
let vertex = this.vertices[u];
callback(vertex);
for (var i = 0; i < vertex.neighbours.length; i++) {
let v = vertex.neighbours[i]
if (!visited[v]) {
visited[v] = true;
queue.push(v);
}
}
}
}
/**
* Get the depth of a subtree in the direction opposite to the vertex specified as the parent vertex.
*
* @param {Number} vertexId A vertex id.
* @param {Number} parentVertexId The id of a neighbouring vertex.
* @returns {Number} The depth of the sub-tree.
*/
getTreeDepth(vertexId, parentVertexId) {
if (vertexId === null || parentVertexId === null) {
return 0;
}
let neighbours = this.vertices[vertexId].getSpanningTreeNeighbours(parentVertexId);
let max = 0;
for (var i = 0; i < neighbours.length; i++) {
let childId = neighbours[i];
let d = this.getTreeDepth(childId, vertexId);
if (d > max) {
max = d;
}
}
return max + 1;
}
/**
* Traverse a sub-tree in the graph.
*
* @param {Number} vertexId A vertex id.
* @param {Number} parentVertexId A neighbouring vertex.
* @param {Function} callback The callback function that is called with each visited as an argument.
* @param {Number} [maxDepth=999999] The maximum depth of the recursion.
* @param {Boolean} [ignoreFirst=false] Whether or not to ignore the starting vertex supplied as vertexId in the callback.
* @param {Number} [depth=1] The current depth in the tree.
* @param {Uint8Array} [visited=null] An array holding a flag on whether or not a node has been visited.
*/
traverseTree(vertexId, parentVertexId, callback, maxDepth = 999999, ignoreFirst = false, depth = 1, visited = null) {
if (visited === null) {
visited = new Uint8Array(this.vertices.length);
}
if (depth > maxDepth + 1 || visited[vertexId] === 1) {
return;
}
visited[vertexId] = 1;
let vertex = this.vertices[vertexId];
let neighbours = vertex.getNeighbours(parentVertexId);
if (!ignoreFirst || depth > 1) {
callback(vertex);
}
for (var i = 0; i < neighbours.length; i++) {
this.traverseTree(neighbours[i], vertexId, callback, maxDepth, ignoreFirst, depth + 1, visited);
}
}
/**
* Positiones the (sub)graph using Kamada and Kawais algorithm for drawing general undirected graphs. https://pdfs.semanticscholar.org/b8d3/bca50ccc573c5cb99f7d201e8acce6618f04.pdf
* There are undocumented layout parameters. They are undocumented for a reason, so be very careful.
*
* @param {Number[]} vertexIds An array containing vertexIds to be placed using the force based layout.
* @param {Vector2} center The center of the layout.
* @param {Number} startVertexId A vertex id. Should be the starting vertex - e.g. the first to be positioned and connected to a previously place vertex.
* @param {Ring} ring The bridged ring associated with this force-based layout.
*/
kkLayout(vertexIds, center, startVertexId, ring, bondLength,
threshold = 0.1, innerThreshold = 0.1, maxIteration = 2000,
maxInnerIteration = 50, maxEnergy = 1e9) {
let edgeStrength = bondLength;
// Add vertices that are directly connected to the ring
var i = vertexIds.length;
while (i--) {
let vertex = this.vertices[vertexIds[i]];
var j = vertex.neighbours.length;
}
let matDist = this.getSubgraphDistanceMatrix(vertexIds);
let length = vertexIds.length;
// Initialize the positions. Place all vertices on a ring around the center
let radius = MathHelper.polyCircumradius(500, length);
let angle = MathHelper.centralAngle(length);
let a = 0.0;
let arrPositionX = new Float32Array(length);
let arrPositionY = new Float32Array(length);
let arrPositioned = Array(length);
i = length;
while (i--) {
let vertex = this.vertices[vertexIds[i]];
if (!vertex.positioned) {
arrPositionX[i] = center.x + Math.cos(a) * radius;
arrPositionY[i] = center.y + Math.sin(a) * radius;
} else {
arrPositionX[i] = vertex.position.x;
arrPositionY[i] = vertex.position.y;
}
arrPositioned[i] = vertex.positioned;
a += angle;
}
// Create the matrix containing the lengths
let matLength = Array(length);
i = length;
while (i--) {
matLength[i] = new Array(length);
var j = length;
while (j--) {
matLength[i][j] = bondLength * matDist[i][j];
}
}
// Create the matrix containing the spring strenghts
let matStrength = Array(length);
i = length;
while (i--) {
matStrength[i] = Array(length);
var j = length;
while (j--) {
matStrength[i][j] = edgeStrength * Math.pow(matDist[i][j], -2.0);
}
}
// Create the matrix containing the energies
let matEnergy = Array(length);
let arrEnergySumX = new Float32Array(length);
let arrEnergySumY = new Float32Array(length);
i = length;
while (i--) {
matEnergy[i] = Array(length);
}
i = length;
let ux, uy, dEx, dEy, vx, vy, denom;
while (i--) {
ux = arrPositionX[i];
uy = arrPositionY[i];
dEx = 0.0;
dEy = 0.0;
let j = length;
while (j--) {
if (i === j) {
continue;
}
vx = arrPositionX[j];
vy = arrPositionY[j];
denom = 1.0 / Math.sqrt((ux - vx) * (ux - vx) + (uy - vy) * (uy - vy));
matEnergy[i][j] = [
matStrength[i][j] * ((ux - vx) - matLength[i][j] * (ux - vx) * denom),
matStrength[i][j] * ((uy - vy) - matLength[i][j] * (uy - vy) * denom)
]
matEnergy[j][i] = matEnergy[i][j];
dEx += matEnergy[i][j][0];
dEy += matEnergy[i][j][1];
}
arrEnergySumX[i] = dEx;
arrEnergySumY[i] = dEy;
}
// Utility functions, maybe inline them later
let energy = function (index) {
return [arrEnergySumX[index] * arrEnergySumX[index] + arrEnergySumY[index] * arrEnergySumY[index], arrEnergySumX[index], arrEnergySumY[index]];
}
let highestEnergy = function () {
let maxEnergy = 0.0;
let maxEnergyId = 0;
let maxDEX = 0.0;
let maxDEY = 0.0
i = length;
while (i--) {
let [delta, dEX, dEY] = energy(i);
if (delta > maxEnergy && arrPositioned[i] === false) {
maxEnergy = delta;
maxEnergyId = i;
maxDEX = dEX;
maxDEY = dEY;
}
}
return [maxEnergyId, maxEnergy, maxDEX, maxDEY];
}
let update = function (index, dEX, dEY) {
let dxx = 0.0;
let dyy = 0.0;
let dxy = 0.0;
let ux = arrPositionX[index];
let uy = arrPositionY[index];
let arrL = matLength[index];
let arrK = matStrength[index];
i = length;
while (i--) {
if (i === index) {
continue;
}
let vx = arrPositionX[i];
let vy = arrPositionY[i];
let l = arrL[i];
let k = arrK[i];
let m = (ux - vx) * (ux - vx);
let denom = 1.0 / Math.pow(m + (uy - vy) * (uy - vy), 1.5);
dxx += k * (1 - l * (uy - vy) * (uy - vy) * denom);
dyy += k * (1 - l * m * denom);
dxy += k * (l * (ux - vx) * (uy - vy) * denom);
}
// Prevent division by zero
if (dxx === 0) {
dxx = 0.1;
}
if (dyy === 0) {
dyy = 0.1;
}
if (dxy === 0) {
dxy = 0.1;
}
let dy = (dEX / dxx + dEY / dxy);
dy /= (dxy / dxx - dyy / dxy); // had to split this onto two lines because the syntax highlighter went crazy.
let dx = -(dxy * dy + dEX) / dxx;
arrPositionX[index] += dx;
arrPositionY[index] += dy;
// Update the energies
let arrE = matEnergy[index];
dEX = 0.0;
dEY = 0.0;
ux = arrPositionX[index];
uy = arrPositionY[index];
let vx, vy, prevEx, prevEy, denom;
i = length;
while (i--) {
if (index === i) {
continue;
}
vx = arrPositionX[i];
vy = arrPositionY[i];
// Store old energies
prevEx = arrE[i][0];
prevEy = arrE[i][1];
denom = 1.0 / Math.sqrt((ux - vx) * (ux - vx) + (uy - vy) * (uy - vy));
dx = arrK[i] * ((ux - vx) - arrL[i] * (ux - vx) * denom);
dy = arrK[i] * ((uy - vy) - arrL[i] * (uy - vy) * denom);
arrE[i] = [dx, dy];
dEX += dx;
dEY += dy;
arrEnergySumX[i] += dx - prevEx;
arrEnergySumY[i] += dy - prevEy;
}
arrEnergySumX[index] = dEX;
arrEnergySumY[index] = dEY;
}
// Setting up variables for the while loops
let maxEnergyId = 0;
let dEX = 0.0;
let dEY = 0.0;
let delta = 0.0;
let iteration = 0;
let innerIteration = 0;
while (maxEnergy > threshold && maxIteration > iteration) {
iteration++;
[maxEnergyId, maxEnergy, dEX, dEY] = highestEnergy();
delta = maxEnergy;
innerIteration = 0;
while (delta > innerThreshold && maxInnerIteration > innerIteration) {
innerIteration++;
update(maxEnergyId, dEX, dEY);
[delta, dEX, dEY] = energy(maxEnergyId);
}
}
i = length;
while (i--) {
let index = vertexIds[i];
let vertex = this.vertices[index];
vertex.position.x = arrPositionX[i];
vertex.position.y = arrPositionY[i];
vertex.positioned = true;
vertex.forcePositioned = true;
}
}
/**
* PRIVATE FUNCTION used by getBridges().
*/
_bridgeDfs(u, visited, disc, low, parent, adj, outBridges) {
visited[u] = true;
disc[u] = low[u] = ++this._time;
for (var i = 0; i < adj[u].length; i++) {
let v = adj[u][i];
if (!visited[v]) {
parent[v] = u;
this._bridgeDfs(v, visited, disc, low, parent, adj, outBridges);
low[u] = Math.min(low[u], low[v]);
// If low > disc, we have a bridge
if (low[v] > disc[u]) {
outBridges.push([u, v]);
}
} else if (v !== parent[u]) {
low[u] = Math.min(low[u], disc[v]);
}
}
}
/**
* Returns the connected components of the graph.
*
* @param {Array[]} adjacencyMatrix An adjacency matrix.
* @returns {Set[]} Connected components as sets.
*/
static getConnectedComponents(adjacencyMatrix) {
let length = adjacencyMatrix.length;
let visited = new Array(length);
let components = new Array();
let count = 0;
visited.fill(false);
for (var u = 0; u < length; u++) {
if (!visited[u]) {
let component = Array();
visited[u] = true;
component.push(u);
count++;
Graph._ccGetDfs(u, visited, adjacencyMatrix, component);
if (component.length > 1) {
components.push(component);
}
}
}
return components;
}
/**
* Returns the number of connected components for the graph.
*
* @param {Array[]} adjacencyMatrix An adjacency matrix.
* @returns {Number} The number of connected components of the supplied graph.
*/
static getConnectedComponentCount(adjacencyMatrix) {
let length = adjacencyMatrix.length;
let visited = new Array(length);
let count = 0;
visited.fill(false);
for (var u = 0; u < length; u++) {
if (!visited[u]) {
visited[u] = true;
count++;
Graph._ccCountDfs(u, visited, adjacencyMatrix);
}
}
return count;
}
/**
* PRIVATE FUNCTION used by getConnectedComponentCount().
*/
static _ccCountDfs(u, visited, adjacencyMatrix) {
for (var v = 0; v < adjacencyMatrix[u].length; v++) {
let c = adjacencyMatrix[u][v];
if (!c || visited[v] || u === v) {
continue;
}
visited[v] = true;
Graph._ccCountDfs(v, visited, adjacencyMatrix);
}
}
/**
* PRIVATE FUNCTION used by getConnectedComponents().
*/
static _ccGetDfs(u, visited, adjacencyMatrix, component) {
for (var v = 0; v < adjacencyMatrix[u].length; v++) {
let c = adjacencyMatrix[u][v];
if (!c || visited[v] || u === v) {
continue;
}
visited[v] = true;
component.push(v);
Graph._ccGetDfs(v, visited, adjacencyMatrix, component);
}
}
}
module.exports = Graph