Przeglądaj źródła

NEW: refactor structure

Pavel Zhigalov 3 tygodni temu
rodzic
commit
ced0fea279

+ 1 - 1
.gitignore

@@ -1,4 +1,4 @@
 node_modules
 dist/**
 .idea/**
-stuff/**
+data/**

+ 16 - 0
src/index.ts

@@ -0,0 +1,16 @@
+import { readFileSync } from "node:fs";
+import Edge from "./v1/graph/Edge.js";
+import Graph from "./v1/graph/Graph.js";
+import Node from "./v1/graph/Node.js";
+import JointJsonDeserializer from "./v1/io/deserialize/json/JointJsonDeserializer.js";
+import { JsonDeserializer } from "./v1/io/deserialize/json/JsonDeserializer.js";
+import DefaultGraphService from "./v1/services/DefaultGraphService.js";
+import SigiuyamaAlgorithm from "./v1/optimizer/sugiyama/SugiyamaAlgorithm.js";
+import CycleRemoveStep from "./v1/optimizer/sugiyama/CycleRemoveStep.js";
+
+const deserializer: JsonDeserializer<Graph<Node, Edge<Node>>> = new JointJsonDeserializer();
+const graph = deserializer.deserialize(readFileSync("./data/graph.json", "utf-8"));
+const service = new DefaultGraphService()
+console.log(service.getNodeDegree(graph, "3"))
+
+new SigiuyamaAlgorithm().addStep(new CycleRemoveStep())

+ 8 - 0
src/v1/errors/DeserializerError.ts

@@ -0,0 +1,8 @@
+import BPMNError from "./BPMNError.js";
+
+export default class DeserializerError extends BPMNError {
+	public constructor(message: string) {
+		super(message);
+		this.name = 'DeserializerError';
+	}
+}

+ 20 - 0
src/v1/graph/Edge.ts

@@ -0,0 +1,20 @@
+import { object, string } from "valibot";
+import Node, { NodeSchema } from "./Node.js";
+
+export default class Edge<TNode extends Node> {
+	public readonly id: string;
+	public from: TNode["id"];
+	public to: TNode["id"];
+	
+	public constructor(from: TNode["id"], to: TNode["id"], id?: string) {
+		this.from = from;
+		this.to = to;
+		this.id = id || crypto.randomUUID();
+	}
+}
+
+export const EdgeSchema = object({
+	id: string(),
+	from: NodeSchema.entries.id,
+	to: NodeSchema.entries.id
+})

+ 84 - 0
src/v1/graph/Graph.ts

@@ -0,0 +1,84 @@
+import { pipe, object, array, forward, partialCheck } from "valibot";
+import Edge, { EdgeSchema } from "./Edge.js";
+import Node, { NodeSchema } from "./Node.js";
+
+export default class Graph<TNode extends Node, TEdge extends Edge<TNode>> {
+	private readonly _nodes: TNode[];
+	private readonly _edges: TEdge[];
+	private readonly _adjacencyList: Map<TNode["id"], Array<TNode["id"]>>;
+
+	public constructor(nodes: TNode[], edges: TEdge[]) {
+		this._nodes = nodes;
+		this._edges = edges;
+		this._adjacencyList = new Map();
+
+		for(const node of nodes)
+			this._adjacencyList.set(node.id, []);
+
+		for(const { from, to } of edges) {
+			const adjacencyNodes = this._adjacencyList.get(from);
+			if(adjacencyNodes)
+				adjacencyNodes.push(to)
+			else
+				this._adjacencyList.set(from, [to])
+		}
+	}
+
+	public getNodes() : TNode[] {
+		return this._nodes;
+	}
+
+	public getNode(nodeId: TNode["id"]): TNode | null {
+		return this._nodes.find((node) => node.id == nodeId) || null;
+	}
+
+	public getEdges() : TEdge[] {
+		return this._edges;
+	}
+
+	public getEdge(edgeId: TEdge["id"]): TEdge | null {
+		return this._edges.find((edge) => edge.id == edgeId) || null;
+	}
+
+	public getNodeNeighbours(nodeId: TNode["id"]): TNode[] {
+		const neighbours: TNode[] = [];
+		const neighbourIds = this._adjacencyList.get(nodeId);
+
+		if(!neighbourIds)
+			return [];
+
+		neighbourIds.forEach((id) => {
+			const node = this._nodes.find((n) => n.id == id);
+			if(!node)
+				return;
+
+			neighbours.push(node);
+		})
+
+		return neighbours;
+	}
+}
+
+export const GraphSchema = pipe(
+	object({
+		nodes: array(NodeSchema),
+		edges: array(EdgeSchema)
+	}),
+	forward(
+		partialCheck(
+			[['nodes'], ['edges']],
+			(input) => {
+				const { nodes, edges } = input;
+				for(const edge of edges) {
+					const { from, to } = edge;
+					if(!(nodes.some((node) => node.id == from) && nodes.some((node) => node.id == to)))
+						return false;
+				}
+
+				return true;
+			},
+			`Some of edges contains link to a node that doesn't exist in "nodes" array`
+		),
+		['edges']
+	)
+)

+ 19 - 0
src/v1/graph/Node.ts

@@ -0,0 +1,19 @@
+import { number, object, string } from "valibot";
+
+export default class Node {
+	public readonly id: string;
+	public x: number;
+	public y: number;
+
+	public constructor(x: number, y: number, id?: string) {
+		this.id = id || crypto.randomUUID();
+		this.x = x;
+		this.y = y;
+	}
+}
+
+export const NodeSchema = object({
+	id: string(),
+	x: number(),
+	y: number()
+})

+ 3 - 0
src/v1/io/deserialize/Deserializer.ts

@@ -0,0 +1,3 @@
+export default interface Deserializer<TInput, TOutput> {
+	deserialize(input: TInput): TOutput;
+}

+ 25 - 0
src/v1/io/deserialize/json/JointJsonDeserializer.ts

@@ -0,0 +1,25 @@
+import { InferOutput, parse } from "valibot";
+import DeserializerError from "../../../errors/DeserializerError.js";
+import Edge from "../../../graph/Edge.js";
+import Graph, { GraphSchema } from "../../../graph/Graph.js";
+import Node from "../../../graph/Node.js";
+import { JsonDeserializer } from "./JsonDeserializer.js";
+
+export default class JointJsonDeserializer implements JsonDeserializer<Graph<Node, Edge<Node>>> {
+	deserialize(input: string) : Graph<Node, Edge<Node>> {
+		const parsed = JSON.parse(input);
+
+		if(!parsed)
+			throw new DeserializerError("Result of parsing graph structure not found");
+
+		let graph: InferOutput<typeof GraphSchema>;
+		try {
+			graph = parse(GraphSchema, parsed);
+		// eslint-disable-next-line @typescript-eslint/no-explicit-any
+		} catch (error: any) {
+			throw new DeserializerError(error.message)
+		}
+
+		return new Graph(graph.nodes, graph.edges);
+	}
+}

+ 3 - 0
src/v1/io/deserialize/json/JsonDeserializer.ts

@@ -0,0 +1,3 @@
+import Deserializer from "../Deserializer.js";
+
+export type JsonDeserializer<TOutput> = Deserializer<string, TOutput>

+ 8 - 0
src/v1/optimizer/AlgorithmContext.ts

@@ -0,0 +1,8 @@
+import Edge from "../graph/Edge.js"
+import Graph from "../graph/Graph.js"
+import Node from "../graph/Node.js"
+
+export type AlgorithmContext = {
+	graph: Graph<Node, Edge<Node>>,
+	config: object
+}

+ 5 - 0
src/v1/optimizer/AlgorithmStep.ts

@@ -0,0 +1,5 @@
+import { AlgorithmContext } from "./AlgorithmContext.js";
+
+export default interface AlgorithmStep<C extends AlgorithmContext> {
+	run(context: C): void
+}

+ 8 - 0
src/v1/optimizer/sugiyama/CycleRemoveStep.ts

@@ -0,0 +1,8 @@
+import Step from "../AlgorithmStep.js";
+import { SiguiyamaContext } from "./SugiyamaContext.js";
+
+export default class CycleRemoveStep implements Step<SiguiyamaContext> {
+	run(context: SiguiyamaContext): void {
+		throw new Error("Method not implemented.");
+	}
+}

+ 24 - 0
src/v1/optimizer/sugiyama/SugiyamaAlgorithm.ts

@@ -0,0 +1,24 @@
+import Edge from "../../graph/Edge.js";
+import Graph from "../../graph/Graph.js";
+import Node from "../../graph/Node.js";
+import Step from "../AlgorithmStep.js";
+import { SiguiyamaContext } from "./SugiyamaContext.js";
+
+export default class SigiuyamaAlgorithm {
+	private readonly _steps: Step<SiguiyamaContext>[] = [];
+
+	public addStep(step: Step<SiguiyamaContext>): this {
+		this._steps.push(step)
+		return this;
+	}
+
+	public run(graph: Graph<Node, Edge<Node>>): SiguiyamaContext {
+		const context: SiguiyamaContext = { graph, config: {} };
+
+		this._steps.forEach((step) => {
+			step.run(context);
+		})
+
+		return context;
+	}
+}

+ 7 - 0
src/v1/optimizer/sugiyama/SugiyamaContext.ts

@@ -0,0 +1,7 @@
+import Edge from "../../graph/Edge.js"
+import Node from "../../graph/Node.js"
+import { AlgorithmContext } from "../AlgorithmContext.js"
+
+export type SiguiyamaContext = AlgorithmContext & {
+	reversedEdges?: Edge<Node>[]
+}

+ 10 - 0
src/v1/services/DefaultGraphService.ts

@@ -0,0 +1,10 @@
+import Edge from "../graph/Edge.js";
+import Graph from "../graph/Graph.js";
+import Node from "../graph/Node.js";
+import GraphService from "./GraphService.js";
+
+export default class DefaultGraphService implements GraphService<Node, Edge<Node>, Graph<Node, Edge<Node>>> {
+	getNodeDegree(graph: Graph<Node, Edge<Node>>, nodeId: string): number {
+		return graph.getNodeNeighbours(nodeId).length;
+	}
+}

+ 7 - 0
src/v1/services/GraphService.ts

@@ -0,0 +1,7 @@
+import Edge from "../graph/Edge.js";
+import Graph from "../graph/Graph.js";
+import Node from "../graph/Node.js";
+
+export default interface GraphService<TNode extends Node = Node, TEdge extends Edge<TNode> = Edge<TNode>, TGraph extends Graph<TNode, TEdge> = Graph<TNode, TEdge>> {
+	getNodeDegree(graph: TGraph, nodeId: TNode["id"]) : number;
+}