UNPKG

graph-by-ivan-tulaev

Version:

Graph library for traversing and processing any directional graphs.

213 lines (212 loc) 7.62 kB
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 };