@loken/hierarchies
Version:
Library for working with hierarchies of identifiers and identifiable objects.
129 lines (99 loc) • 2.6 kB
text/typescript
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
}