2023-08-02 18:24:17 +03:00
|
|
|
// ======== ID based fast Graph implementation =============
|
2023-07-29 21:23:18 +03:00
|
|
|
export class GraphNode {
|
|
|
|
id: number;
|
2023-08-02 18:24:17 +03:00
|
|
|
outputs: number[];
|
|
|
|
inputs: number[];
|
2023-07-29 21:23:18 +03:00
|
|
|
|
|
|
|
constructor(id: number) {
|
|
|
|
this.id = id;
|
2023-08-02 18:24:17 +03:00
|
|
|
this.outputs = [];
|
|
|
|
this.inputs = [];
|
2023-07-29 21:23:18 +03:00
|
|
|
}
|
|
|
|
|
2023-08-02 18:24:17 +03:00
|
|
|
addOutput(node: number): void {
|
|
|
|
this.outputs.push(node);
|
2023-07-29 21:23:18 +03:00
|
|
|
}
|
|
|
|
|
2023-08-02 18:24:17 +03:00
|
|
|
addInput(node: number): void {
|
|
|
|
this.inputs.push(node);
|
|
|
|
}
|
|
|
|
|
|
|
|
removeInput(target: number): number | null {
|
|
|
|
const index = this.inputs.findIndex(node => node === target);
|
|
|
|
return index > -1 ? this.inputs.splice(index, 1)[0] : null;
|
|
|
|
}
|
|
|
|
|
|
|
|
removeOutput(target: number): number | null {
|
|
|
|
const index = this.outputs.findIndex(node => node === target);
|
|
|
|
return index > -1 ? this.outputs.splice(index, 1)[0] : null;
|
2023-07-29 21:23:18 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export class Graph {
|
|
|
|
nodes: Map<number, GraphNode> = new Map();
|
|
|
|
|
2023-08-02 18:24:17 +03:00
|
|
|
constructor(arr?: number[][]) {
|
|
|
|
if (!arr) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
arr.forEach(edge => {
|
|
|
|
if (edge.length == 1) {
|
|
|
|
this.addNode(edge[0]);
|
|
|
|
} else {
|
|
|
|
this.addEdge(edge[0], edge[1]);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2023-07-29 21:23:18 +03:00
|
|
|
|
|
|
|
addNode(target: number): GraphNode {
|
|
|
|
let node = this.nodes.get(target);
|
|
|
|
if (!node) {
|
|
|
|
node = new GraphNode(target);
|
|
|
|
this.nodes.set(target, node);
|
|
|
|
}
|
|
|
|
return node;
|
|
|
|
}
|
|
|
|
|
|
|
|
removeNode(target: number): GraphNode | null {
|
|
|
|
const nodeToRemove = this.nodes.get(target);
|
|
|
|
if (!nodeToRemove) {
|
|
|
|
return null;
|
|
|
|
}
|
2023-08-02 18:24:17 +03:00
|
|
|
this.nodes.forEach(node => {
|
|
|
|
node.removeInput(nodeToRemove.id);
|
|
|
|
node.removeOutput(nodeToRemove.id);
|
2023-07-29 21:23:18 +03:00
|
|
|
});
|
|
|
|
this.nodes.delete(target);
|
|
|
|
return nodeToRemove;
|
|
|
|
}
|
|
|
|
|
|
|
|
addEdge(source: number, destination: number): void {
|
|
|
|
const sourceNode = this.addNode(source);
|
|
|
|
const destinationNode = this.addNode(destination);
|
2023-08-02 18:24:17 +03:00
|
|
|
sourceNode.addOutput(destinationNode.id);
|
|
|
|
destinationNode.addInput(sourceNode.id);
|
2023-07-29 21:23:18 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
removeEdge(source: number, destination: number): void {
|
|
|
|
const sourceNode = this.nodes.get(source);
|
|
|
|
const destinationNode = this.nodes.get(destination);
|
|
|
|
if (sourceNode && destinationNode) {
|
2023-08-02 18:24:17 +03:00
|
|
|
sourceNode.removeOutput(destination);
|
|
|
|
destinationNode.removeInput(source);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
expandOutputs(origin: number[]): number[] {
|
|
|
|
const result: number[] = [];
|
|
|
|
const marked = new Map<number, boolean>();
|
|
|
|
origin.forEach(id => marked.set(id, true));
|
|
|
|
origin.forEach(id => {
|
|
|
|
const node = this.nodes.get(id);
|
|
|
|
if (node) {
|
|
|
|
node.outputs.forEach(child => {
|
|
|
|
if (!marked.get(child) && !result.find(id => id === child)) {
|
|
|
|
result.push(child);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
let position = 0;
|
|
|
|
while (position < result.length) {
|
|
|
|
const node = this.nodes.get(result[position]);
|
|
|
|
if (node && !marked.get(node.id)) {
|
|
|
|
marked.set(node.id, true);
|
|
|
|
node.outputs.forEach(child => {
|
|
|
|
if (!marked.get(child) && !result.find(id => id === child)) {
|
|
|
|
result.push(child);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
position += 1;
|
2023-07-29 21:23:18 +03:00
|
|
|
}
|
2023-08-02 18:24:17 +03:00
|
|
|
return result;
|
2023-07-29 21:23:18 +03:00
|
|
|
}
|
2023-08-02 18:24:17 +03:00
|
|
|
|
|
|
|
expandInputs(origin: number[]): number[] {
|
|
|
|
const result: number[] = [];
|
|
|
|
const marked = new Map<number, boolean>();
|
|
|
|
origin.forEach(id => marked.set(id, true));
|
|
|
|
origin.forEach(id => {
|
|
|
|
const node = this.nodes.get(id);
|
|
|
|
if (node) {
|
|
|
|
node.inputs.forEach(child => {
|
|
|
|
if (!marked.get(child) && !result.find(id => id === child)) {
|
|
|
|
result.push(child);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
let position = 0;
|
|
|
|
while (position < result.length) {
|
|
|
|
const node = this.nodes.get(result[position]);
|
|
|
|
if (node && !marked.get(node.id)) {
|
|
|
|
marked.set(node.id, true);
|
|
|
|
node.inputs.forEach(child => {
|
|
|
|
if (!marked.get(child) && !result.find(id => id === child)) {
|
|
|
|
result.push(child);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
position += 1;
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
2023-07-29 21:23:18 +03:00
|
|
|
|
|
|
|
visitDFS(visitor: (node: GraphNode) => void) {
|
|
|
|
const visited: Map<number, boolean> = new Map();
|
|
|
|
this.nodes.forEach(node => {
|
|
|
|
if (!visited.has(node.id)) {
|
2023-08-02 18:24:17 +03:00
|
|
|
this.depthFirstSearch(node, visited, visitor);
|
2023-07-29 21:23:18 +03:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2023-08-02 18:24:17 +03:00
|
|
|
private depthFirstSearch(
|
|
|
|
node: GraphNode,
|
|
|
|
visited: Map<number, boolean>,
|
|
|
|
visitor: (node: GraphNode) => void)
|
|
|
|
: void {
|
2023-07-29 21:23:18 +03:00
|
|
|
visited.set(node.id, true);
|
|
|
|
visitor(node);
|
2023-08-02 18:24:17 +03:00
|
|
|
node.outputs.forEach((item) => {
|
2023-07-29 21:23:18 +03:00
|
|
|
if (!visited.has(item)) {
|
2023-08-02 18:24:17 +03:00
|
|
|
const childNode = this.nodes.get(item)!;
|
|
|
|
this.depthFirstSearch(childNode, visited, visitor);
|
2023-07-29 21:23:18 +03:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|