/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ export class Node { readonly data: T; readonly incoming = new Map>(); readonly outgoing = new Map>(); constructor(data: T) { this.data = data; } } export class Graph { private readonly _nodes = new Map>(); constructor(private readonly _hashFn: (element: T) => string) { // empty } roots(): Node[] { const ret: Node[] = []; for (let node of this._nodes.values()) { if (node.outgoing.size === 0) { ret.push(node); } } return ret; } insertEdge(from: T, to: T): void { const fromNode = this.lookupOrInsertNode(from); const toNode = this.lookupOrInsertNode(to); fromNode.outgoing.set(this._hashFn(to), toNode); toNode.incoming.set(this._hashFn(from), fromNode); } removeNode(data: T): void { const key = this._hashFn(data); this._nodes.delete(key); for (let node of this._nodes.values()) { node.outgoing.delete(key); node.incoming.delete(key); } } lookupOrInsertNode(data: T): Node { const key = this._hashFn(data); let node = this._nodes.get(key); if (!node) { node = new Node(data); this._nodes.set(key, node); } return node; } isEmpty(): boolean { return this._nodes.size === 0; } toString(): string { let data: string[] = []; for (let [key, value] of this._nodes) { data.push( `${key}, (incoming)[${[...value.incoming.keys()].join( ', ' )}], (outgoing)[${[...value.outgoing.keys()].join(',')}]` ); } return data.join('\n'); } /** * This is brute force and slow and **only** be used * to trouble shoot. */ findCycleSlow() { for (let [id, node] of this._nodes) { const seen = new Set([id]); const res = this._findCycle(node, seen); if (res) { return res; } } return undefined; } private _findCycle(node: Node, seen: Set): string | undefined { for (let [id, outgoing] of node.outgoing) { if (seen.has(id)) { return [...seen, id].join(' -> '); } seen.add(id); const value = this._findCycle(outgoing, seen); if (value) { return value; } seen.delete(id); } return undefined; } }