graph-by-ivan-tulaev
Version:
Graph library for traversing and processing any directional graphs.
213 lines (212 loc) • 7.62 kB
JavaScript
function getFirstUnvisited(visited, initialGraph) {
return initialGraph.nodes.find((item) => !visited.has(item));
}
function getLast(executionSequence) {
return executionSequence.pop();
}
function getFirst(executionSequence) {
return executionSequence.shift();
}
function addToEnd(nodes, executionSequence) {
for (const node of nodes) {
executionSequence.push(node);
}
}
function addToStart(nodes, executionSequence) {
for (const node of nodes) {
executionSequence.unshift(node);
}
}
function getNotVisitedOutgoingNodes(node, graph, visited) {
return [...graph.getOutgoingEdgesFor(node)].map((edge) => edge.target).filter((target) => !visited.has(target));
}
function getNotVisitedIncomingNodes(node, graph, visited) {
return [...graph.getIncomingEdgesFor(node)].map((edge) => edge.source).filter((source) => !visited.has(source));
}
class Graph {
notDirected;
_adjacencyList;
constructor(notDirected = false) {
this.notDirected = notDirected;
this._adjacencyList = /* @__PURE__ */ new Map();
}
get nodes() {
return [...this._adjacencyList.keys()];
}
addNode(node) {
if (!this.nodes.includes(node)) {
this._adjacencyList.set(node, { incoming: /* @__PURE__ */ new Set(), outgoing: /* @__PURE__ */ new Set() });
}
}
deleteNode(node) {
this._adjacencyList.delete(node);
for (const [, value] of this._adjacencyList) {
const { incoming, outgoing } = value;
const allEdges = /* @__PURE__ */ new Set([...incoming, ...outgoing]);
for (const edge of allEdges) {
if (edge.source !== node && edge.target !== node) continue;
incoming.delete(edge);
outgoing.delete(edge);
}
}
}
get edges() {
return new Set([...this._adjacencyList.values()].map((item) => [...item.incoming, ...item.outgoing]).flat());
}
addEdge(edge) {
const { source, target } = edge;
if (!this._adjacencyList.has(source) || !this._adjacencyList.has(target)) throw new Error(`Cannot add edge: has no node ${JSON.stringify(source)} or ${JSON.stringify(target)}`);
const incoming = this._adjacencyList.get(target);
if (incoming) {
incoming.incoming.add(edge);
}
const outgoing = this._adjacencyList.get(source);
if (outgoing) {
outgoing.outgoing.add(edge);
}
}
deleteEdge(edge) {
const { source, target } = edge;
const incoming = this._adjacencyList.get(target);
if (incoming) {
incoming.incoming.delete(edge);
}
const outgoing = this._adjacencyList.get(source);
if (outgoing) {
outgoing.outgoing.delete(edge);
}
}
getIncomingEdgesFor(node) {
return this._adjacencyList.get(node)?.incoming || /* @__PURE__ */ new Set();
}
getOutgoingEdgesFor(node) {
return this._adjacencyList.get(node)?.outgoing || /* @__PURE__ */ new Set();
}
/**
*
* @param getNextNodes get not visited outgoing elements by default
* @param getStartElement get first unvisited by default
* @param getNextFromExecutionSequence get last by default
* @param addNextNodesToExecutionSequence add to end by default
* @param executeCurrent
*/
genericTraversing(getNextNodes = getNotVisitedOutgoingNodes, getStartElement = getFirstUnvisited, getNextFromExecutionSequence = getLast, addNextNodesToExecutionSequence = addToEnd, executeCurrent) {
const visited = /* @__PURE__ */ new Set();
while (visited.size < this.nodes.length) {
const nextStart = getStartElement(visited, this);
if (!nextStart) break;
const executionSequence = [nextStart];
while (executionSequence.length > 0) {
const currentNode = getNextFromExecutionSequence(executionSequence);
if (!currentNode) throw new Error(`Can't get current node from ${executionSequence}`);
if (executeCurrent) executeCurrent(currentNode);
visited.add(currentNode);
const nextNodes = getNextNodes(currentNode, this, visited, executionSequence);
if (!nextNodes) break;
addNextNodesToExecutionSequence(nextNodes, executionSequence);
}
}
}
createCopy() {
const newGraph = new Graph();
for (const node of this.nodes) {
newGraph.addNode(node);
}
for (const edge of this.edges) {
newGraph.addEdge(edge);
}
return newGraph;
}
// TODO: ADD OPTIMISATION
getSeparatedGraphs() {
const separatedGraphs = /* @__PURE__ */ new Set();
const getLocalGraph = (node, graph) => {
const outgoingEdges = graph.getOutgoingEdgesFor(node);
const outgoingNodes = [...outgoingEdges].map((edge) => edge.target);
const incomingEdges = graph.getIncomingEdgesFor(node);
const incomingNodes = [...incomingEdges].map((edge) => edge.source);
const allEdges = /* @__PURE__ */ new Set([...outgoingEdges, ...incomingEdges]);
const allNodes = /* @__PURE__ */ new Set([...outgoingNodes, ...incomingNodes]);
const localGraph = new Graph(false);
localGraph.addNode(node);
for (const localNode of allNodes) {
localGraph.addNode(localNode);
}
for (const localEdge of allEdges) {
localGraph.addEdge(localEdge);
}
return localGraph;
};
const getGraphsToMerge = (separatedGraphs2, localGraph) => {
const graphsToMerge = /* @__PURE__ */ new Set();
for (const localNode of localGraph.nodes) {
for (const separatedGraph of separatedGraphs2) {
if (separatedGraph.nodes.includes(localNode)) {
graphsToMerge.add(separatedGraph);
break;
}
}
}
return graphsToMerge;
};
const getAllNotVisitedAdjacentNodes = (node, graph, visited) => {
const localGraph = getLocalGraph(node, graph);
const graphsToMerge = getGraphsToMerge(separatedGraphs, localGraph);
if (graphsToMerge.size > 0) {
for (const graphToMerge of graphsToMerge) {
separatedGraphs.delete(graphToMerge);
}
const mergedGraphs = Graph.mergeGraphs(graphsToMerge.add(localGraph));
separatedGraphs.add(mergedGraphs);
} else {
separatedGraphs.add(localGraph);
}
return [...localGraph.nodes].filter((item) => !visited.has(item));
};
this.genericTraversing(getAllNotVisitedAdjacentNodes, getFirstUnvisited, getLast, addToEnd);
return [...separatedGraphs];
}
// TODO: ADD OPTIMISATION for self loop
isNodeTraced(node, to, backward = false) {
let isTraced = false;
const checkedEnds = [to].flat();
const getStart = (visited) => {
return !visited.has(node) ? node : void 0;
};
const execCurrent = (curNode) => {
if (node === curNode && checkedEnds.includes(curNode)) isTraced = true;
};
const getNext = (node2, graph, visited) => {
const notVisited = !backward ? getNotVisitedOutgoingNodes(node2, graph, visited) : getNotVisitedIncomingNodes(node2, graph, visited);
if (notVisited.some((notVisitedNode) => checkedEnds.includes(notVisitedNode))) {
isTraced = true;
return;
}
return notVisited;
};
this.genericTraversing(getNext, getStart, getLast, addToEnd, execCurrent);
return isTraced;
}
static mergeGraphs(graphs) {
const resultGraph = new Graph();
for (const graph of graphs) {
for (const node of graph.nodes) {
resultGraph.addNode(node);
}
for (const edge of graph.edges) {
resultGraph.addEdge(edge);
}
}
return resultGraph;
}
}
export {
Graph,
addToEnd,
addToStart,
getFirst,
getFirstUnvisited,
getLast,
getNotVisitedIncomingNodes,
getNotVisitedOutgoingNodes
};