|
@@ -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']
|
|
|
|
|
+ )
|
|
|
|
|
+)
|