ConceptPortal-public/rsconcept/frontend/src/models/Graph.ts

297 lines
7.6 KiB
TypeScript
Raw Normal View History

/**
* Module: Custom graph data structure.
*/
2023-09-21 14:58:01 +03:00
/**
* Represents single node of a {@link Graph}, as implemented by storing outgoing and incoming connections.
*/
2023-07-29 21:23:18 +03:00
export class GraphNode {
id: number;
outputs: number[];
inputs: number[];
2023-07-29 21:23:18 +03:00
constructor(id: number) {
2023-07-29 21:23:18 +03:00
this.id = id;
this.outputs = [];
this.inputs = [];
2023-07-29 21:23:18 +03:00
}
2023-08-03 16:42:49 +03:00
clone(): GraphNode {
const result = new GraphNode(this.id);
2023-12-28 14:04:44 +03:00
result.outputs = [...this.outputs];
result.inputs = [...this.inputs];
2023-08-03 16:42:49 +03:00
return result;
}
addOutput(node: number): void {
this.outputs.push(node);
2023-07-29 21:23:18 +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
}
}
2023-09-21 14:58:01 +03:00
/**
* Represents a Graph.
2023-12-28 14:04:44 +03:00
*
2023-09-21 14:58:01 +03:00
* This class is optimized for TermGraph use case and not supposed to be used as generic graph implementation.
*/
2023-07-29 21:23:18 +03:00
export class Graph {
nodes: Map<number, GraphNode> = new Map();
2023-07-29 21:23:18 +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
2023-08-03 16:42:49 +03:00
clone(): Graph {
const result = new Graph();
this.nodes.forEach(node => result.nodes.set(node.id, node.clone()));
return result;
}
at(target: number): GraphNode | undefined {
return this.nodes.get(target);
}
addNode(target: number): GraphNode {
2023-07-29 21:23:18 +03:00
let node = this.nodes.get(target);
if (!node) {
node = new GraphNode(target);
this.nodes.set(target, node);
}
return node;
}
hasNode(target: number): boolean {
2023-08-16 00:39:16 +03:00
return !!this.nodes.get(target);
}
removeNode(target: number): GraphNode | null {
2023-07-29 21:23:18 +03:00
const nodeToRemove = this.nodes.get(target);
if (!nodeToRemove) {
return null;
}
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;
}
foldNode(target: number): GraphNode | null {
2023-08-16 00:39:16 +03:00
const nodeToRemove = this.nodes.get(target);
if (!nodeToRemove) {
return null;
}
nodeToRemove.inputs.forEach(input => {
nodeToRemove.outputs.forEach(output => {
this.addEdge(input, output);
2023-12-28 14:04:44 +03:00
});
2023-08-16 00:39:16 +03:00
});
return this.removeNode(target);
}
2023-08-03 16:42:49 +03:00
removeIsolated(): GraphNode[] {
const result: GraphNode[] = [];
this.nodes.forEach(node => {
if (node.outputs.length === 0 && node.inputs.length === 0) {
2024-04-09 13:47:18 +03:00
result.push(node);
2023-08-03 16:42:49 +03:00
this.nodes.delete(node.id);
}
});
return result;
}
addEdge(source: number, destination: number): void {
2023-07-29 21:23:18 +03:00
const sourceNode = this.addNode(source);
const destinationNode = this.addNode(destination);
sourceNode.addOutput(destinationNode.id);
destinationNode.addInput(sourceNode.id);
2023-07-29 21:23:18 +03:00
}
removeEdge(source: number, destination: number): void {
2023-07-29 21:23:18 +03:00
const sourceNode = this.nodes.get(source);
const destinationNode = this.nodes.get(destination);
if (sourceNode && destinationNode) {
sourceNode.removeOutput(destination);
destinationNode.removeInput(source);
}
}
hasEdge(source: number, destination: number): boolean {
2023-08-03 16:42:49 +03:00
const sourceNode = this.nodes.get(source);
if (!sourceNode) {
return false;
}
return !!sourceNode.outputs.find(id => id === destination);
}
expandOutputs(origin: number[]): number[] {
const result: number[] = [];
origin.forEach(id => {
const node = this.nodes.get(id);
if (node) {
node.outputs.forEach(child => {
if (!origin.includes(child) && !result.includes(child)) {
result.push(child);
}
});
}
});
return result;
}
expandInputs(origin: number[]): number[] {
const result: number[] = [];
origin.forEach(id => {
const node = this.nodes.get(id);
if (node) {
node.inputs.forEach(child => {
if (!origin.includes(child) && !result.includes(child)) {
result.push(child);
}
});
}
});
return result;
}
expandAllOutputs(origin: number[]): number[] {
const result: number[] = this.expandOutputs(origin);
if (result.length === 0) {
return [];
}
const marked = new Map<number, boolean>();
origin.forEach(id => marked.set(id, true));
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
}
return result;
2023-07-29 21:23:18 +03:00
}
2023-12-28 14:04:44 +03:00
expandAllInputs(origin: number[]): number[] {
const result: number[] = this.expandInputs(origin);
if (result.length === 0) {
return [];
}
const marked = new Map<number, boolean>();
origin.forEach(id => marked.set(id, true));
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-12-28 14:04:44 +03:00
}
2023-07-29 21:23:18 +03:00
maximizePart(origin: number[]): number[] {
const outputs: number[] = this.expandAllOutputs(origin);
const result = [...origin];
this.topologicalOrder()
.filter(id => outputs.includes(id))
.forEach(id => {
const node = this.nodes.get(id);
if (node?.inputs.every(parent => result.includes(parent))) {
result.push(id);
}
});
return result;
}
2023-12-27 19:34:39 +03:00
topologicalOrder(): number[] {
const result: number[] = [];
const marked = new Map<number, boolean>();
const toVisit: number[] = [];
2023-07-29 21:23:18 +03:00
this.nodes.forEach(node => {
2023-08-03 16:42:49 +03:00
if (marked.get(node.id)) {
return;
2023-07-29 21:23:18 +03:00
}
2023-12-28 14:04:44 +03:00
toVisit.push(node.id);
2023-08-03 16:42:49 +03:00
while (toVisit.length > 0) {
const item = toVisit[toVisit.length - 1];
2023-08-03 16:42:49 +03:00
if (marked.get(item)) {
if (!result.find(id => id === item)) {
2023-08-03 16:42:49 +03:00
result.push(item);
2023-12-28 14:04:44 +03:00
}
toVisit.pop();
2023-08-03 16:42:49 +03:00
} else {
marked.set(item, true);
const itemNode = this.nodes.get(item);
if (itemNode && itemNode.outputs.length > 0) {
itemNode.outputs.forEach(child => {
if (!marked.get(child)) {
toVisit.push(child);
}
});
}
}
}
2023-07-29 21:23:18 +03:00
});
2023-08-03 16:42:49 +03:00
return result.reverse();
2023-07-29 21:23:18 +03:00
}
2023-08-03 16:42:49 +03:00
transitiveReduction() {
2023-12-27 19:34:39 +03:00
const order = this.topologicalOrder();
const marked = new Map<number, boolean>();
2023-08-03 16:42:49 +03:00
order.forEach(nodeID => {
if (marked.get(nodeID)) {
return;
}
2023-12-28 14:04:44 +03:00
const stack: { id: number; parents: number[] }[] = [];
stack.push({ id: nodeID, parents: [] });
2023-08-03 16:42:49 +03:00
while (stack.length > 0) {
const item = stack.splice(0, 1)[0];
const node = this.nodes.get(item.id);
if (node) {
node.outputs.forEach(child => {
item.parents.forEach(parent => this.removeEdge(parent, child));
2023-12-28 14:04:44 +03:00
stack.push({ id: child, parents: [item.id, ...item.parents] });
2023-08-03 16:42:49 +03:00
});
}
2023-12-28 14:04:44 +03:00
marked.set(item.id, true);
2023-07-29 21:23:18 +03:00
}
});
2023-12-28 14:04:44 +03:00
}
}