UNPKG

@rimbu/graph

Version:

Immutable Graph data structures for TypeScript

217 lines 7.56 kB
import { TraverseState } from '@rimbu/common'; import { Stream } from '@rimbu/stream'; import { NonEmptyBase } from '@rimbu/collection-types/map-custom'; export class GraphNonEmpty extends NonEmptyBase { constructor(isDirected, context, linkMap, connectionSize) { super(); this.isDirected = isDirected; this.context = context; this.linkMap = linkMap; this.connectionSize = connectionSize; } copy(linkMap, connectionSize) { if (linkMap === this.linkMap && connectionSize === this.connectionSize) return this; return this.context.createNonEmpty(linkMap, connectionSize); } copyE(linkMap, connectionSize) { if (linkMap.nonEmpty()) return this.copy(linkMap, connectionSize); return this.context.empty(); } assumeNonEmpty() { return this; } asNormal() { return this; } forEach(f, options = {}) { const { state = TraverseState() } = options; const mapIter = this.linkMap[Symbol.iterator](); const done = Symbol(); let targetsEntry; while (!state.halted && done !== (targetsEntry = mapIter.fastNext(done))) { const [node, targets] = targetsEntry; if (targets.isEmpty) { f([node], state.nextIndex(), state.halt); } else { const targetsIter = targets[Symbol.iterator](); let target; while (!state.halted && done !== (target = targetsIter.fastNext(done))) { f([node, target], state.nextIndex(), state.halt); } } } } stream() { return this.linkMap.stream().flatMap(([node, targets]) => { if (!targets.nonEmpty()) return [[node]]; return targets .stream() .map((target) => [node, target]); }); } get nodeSize() { return this.linkMap.size; } streamNodes() { return this.linkMap.streamKeys(); } streamConnections() { return this.linkMap .stream() .flatMap(([node1, targets]) => targets.stream().map((node2) => [node1, node2])); } hasNode(node) { return this.linkMap.hasKey(node); } hasConnection(node1, node2) { const targets = this.linkMap.get(node1); return targets?.has(node2) ?? false; } getConnectionStreamFrom(node1) { const targets = this.linkMap.get(node1); if (undefined === targets) return Stream.empty(); return targets.stream().map((node2) => [node1, node2]); } getConnectionStreamTo(node) { if (this.isDirected) { return this.linkMap.stream().collect(([source, targets], _, skip) => { if (!targets?.has(node)) return skip; return [source, node]; }); } const targets = this.linkMap.get(node); if (undefined === targets) return Stream.empty(); return targets.stream().map((node1) => [node1, node]); } getConnectionsFrom(node1) { return this.linkMap.get(node1, this.context.linkConnectionsContext.empty()); } isSink(node) { const targets = this.linkMap.get(node); return targets?.isEmpty ?? false; } isSource(node) { return (this.linkMap.hasKey(node) && this.linkMap.streamValues().every((targets) => !targets.has(node))); } addNode(node) { return this.copy(this.linkMap .modifyAt(node, { ifNew: this.context.linkConnectionsContext.empty }) .assumeNonEmpty(), this.connectionSize); } addNodes(nodes) { const builder = this.toBuilder(); builder.addNodes(nodes); return builder.build().assumeNonEmpty(); } removeNode(node) { const builder = this.toBuilder(); builder.removeNode(node); return builder.build(); } removeNodes(nodes) { const builder = this.toBuilder(); builder.removeNodes(nodes); return builder.build(); } connect(node1, node2) { const newLinkMap = this.linkMap.modifyAt(node1, { ifNew: this.context.linkConnectionsContext.of(node2), ifExists: (targets) => targets.add(node2), }); if (newLinkMap === this.linkMap) return this; const newConnectionSize = this.connectionSize + 1; if (node1 === node2) { return this.context.createNonEmpty(newLinkMap, newConnectionSize); } if (this.isDirected) { return this.copy(newLinkMap .modifyAt(node2, { ifNew: () => this.context.linkConnectionsContext.empty(), }) .assumeNonEmpty(), newConnectionSize); } return this.copy(newLinkMap .modifyAt(node2, { ifNew: () => this.context.linkConnectionsContext.of(node1), ifExists: (targets) => targets.add(node1), }) .assumeNonEmpty(), newConnectionSize); } connectAll(links) { const builder = this.toBuilder(); builder.connectAll(links); return builder.build().assumeNonEmpty(); } disconnect(node1, node2) { if (!this.linkMap.context.isValidKey(node1) || !this.linkMap.context.isValidKey(node2)) return this; const newLinkMap = this.linkMap.updateAt(node1, (targets) => targets.remove(node2)); if (newLinkMap === this.linkMap) return this; const newConnectionSize = this.connectionSize - 1; if (this.isDirected) { return this.copy(newLinkMap, newConnectionSize); } return this.copy(newLinkMap.updateAt(node2, (targets) => targets.remove(node1)), newConnectionSize); } disconnectAll(links) { const builder = this.toBuilder(); builder.disconnectAll(links); return builder.build().assumeNonEmpty(); } removeUnconnectedNodes() { if (!this.isDirected) { const newLinkMap = this.linkMap.filter(([_, targets]) => targets.nonEmpty()); return this.copyE(newLinkMap, this.connectionSize); } const unconnectedNodes = this.linkMap .stream() .collect(([source, targets], _, skip) => { if (targets.isEmpty && !this.linkMap.streamValues().some((t) => t.has(source))) { return source; } return skip; }); return this.removeNodes(unconnectedNodes); } toString() { const connector = this.isDirected ? '->' : '<->'; return this.linkMap.stream().join({ start: `${this.context.typeTag}(\n `, sep: ',\n ', end: '\n)', valueToString: ([node, targets]) => `${node} ${connector} ${targets.stream().join({ start: '[', sep: ', ', end: ']' })}`, }); } toJSON() { return { dataType: this.context.typeTag, value: this.linkMap .stream() .map((entry) => [ entry[0], entry[1] .stream() .map((v) => [v]) .toArray(), ]) .toArray(), }; } toBuilder() { return this.context.createBuilder(this); } } //# sourceMappingURL=non-empty.mjs.map