UNPKG

@loken/hierarchies

Version:

Library for working with hierarchies of identifiers and identifiable objects.

129 lines (99 loc) 2.6 kB
import { type ILinear, LinearQueue, LinearStack, type Some, Stack, type TryResult } from '@loken/utilities'; import { type IGraphSignal, type TraversalType } from './graph.types.js'; /** * @internal Has some members which must be public for internal use * which should not be used by a consumer. */ export class GraphSignal<TNode> implements IGraphSignal<TNode> { //#region fields #visited?: Set<TNode>; #isDepthFirst: boolean; #nodes: ILinear<TNode>; #depth = 0; #branchCount: Stack<number>; #depthCount = 0; #count = 0; #skipped = false; //#endregion //#region IGraphSignal public get depth() { return this.#depth; } public get count() { return this.#count; } public next(nodes: Some<TNode>) { const count = this.#nodes.attach(nodes); if (this.#isDepthFirst && count > 0) this.#branchCount.push(count); } public skip() { this.#skipped = true; } public end() { this.#nodes.clear(); } //endregion //#region internal constructor(options: { roots: Some<TNode>, detectCycles?: boolean, type?: TraversalType }) { if (options.detectCycles ?? false) this.#visited = new Set<TNode>(); this.#isDepthFirst = options.type === 'depth-first'; this.#nodes = this.#isDepthFirst ? new LinearStack<TNode>() : new LinearQueue<TNode>(); this.#depthCount = this.#nodes.attach(options.roots); if (this.#isDepthFirst) { this.#branchCount = new Stack<number>(); this.#branchCount.push(this.#depthCount); } } public shouldYield(): boolean { return !this.#skipped; } public cleanup(): void { if (this.#isDepthFirst) { let res = this.#branchCount.tryPeek(); while (res[1] && res[0] === 0) { this.#branchCount.pop(); res = this.#branchCount.tryPeek(); } } if (!this.#skipped) this.#count++; } public tryGetNext(): TryResult<TNode, string> { if (this.#visited !== undefined) { let res = this.tryGetNextInternal(); while (res[1]) { if (!this.#visited.has(res[0])) { this.#visited.add(res[0]); return res; } res = this.tryGetNextInternal(); } return res; } else { return this.tryGetNextInternal(); } } private tryGetNextInternal(): TryResult<TNode, string> { const res = this.#nodes.tryDetach(); if (!res[1]) return res; this.#skipped = false; if (this.#isDepthFirst) { this.#depth = this.#branchCount.count - 1; this.#branchCount.push(this.#branchCount.pop() - 1); } else { if (this.#depthCount-- == 0) { this.#depth++; this.#depthCount = this.#nodes.count; } } return res; } //#endregion }