UNPKG

@rimbu/graph

Version:

Immutable Graph data structures for TypeScript

327 lines 12.8 kB
import { RimbuError } from '@rimbu/base'; import { OptLazy, OptLazyOr } from '@rimbu/common'; import { Stream } from '@rimbu/stream'; import { ValuedGraphElement } from '../../common/index.mjs'; export class ValuedGraphBuilder { constructor(isDirected, context, source) { this.isDirected = isDirected; this.context = context; this.source = source; this.connectionSize = 0; this._lock = 0; // prettier-ignore this.hasNode = (node) => { if (this.source) return this.source.hasNode(node); return this.linkMap.hasKey(node); }; // prettier-ignore this.hasConnection = (node1, node2) => { if (this.source) return this.source.hasConnection(node1, node2); const targets = this.linkMap.get(node1); return targets?.hasKey(node2) ?? false; }; this.getValue = (node1, node2, otherwise) => { if (undefined !== this.source) { return this.source.getValue(node1, node2, otherwise); } const targets = this.linkMap.get(node1); if (undefined === targets) return OptLazy(otherwise); return targets.get(node2, otherwise); }; this.addNodeInternal = (node) => { const changed = this.linkMap.modifyAt(node, { ifNew: this.context.linkConnectionsContext.builder, }); if (changed) this.source = undefined; return changed; }; this.addNode = (node) => { this.checkLock(); return this.addNodeInternal(node); }; this.addNodes = (nodes) => { this.checkLock(); return (Stream.from(nodes).filterPure({ pred: this.addNodeInternal }).count() > 0); }; // prettier-ignore this.removeNodeInternal = (node) => { const targets = this.linkMap.removeKey(node); if (!targets) return false; this.source = undefined; if (this.isDirected) { this.linkMap.forEach(([sourceNode, targets]) => { if (targets.removeKey(node)) { if (sourceNode !== node) this.connectionSize--; } }); } else { this.connectionSize -= targets.size; targets.forEach(([target]) => this.linkMap.updateAt(target, (values) => { values.removeKey(node); return values; })); } return true; }; // prettier-ignore this.removeNode = (node) => { this.checkLock(); return this.removeNodeInternal(node); }; // prettier-ignore this.removeNodes = (nodes) => { this.checkLock(); return Stream.from(nodes).filterPure({ pred: this.removeNodeInternal }).count() > 0; }; this.connectInternal = (node1, node2, value) => { let changed = false; this.linkMap.modifyAt(node1, { ifNew: () => { const targetBuilder = this.context.linkConnectionsContext.builder(); targetBuilder.set(node2, value); this.connectionSize++; changed = true; return targetBuilder; }, ifExists: (targets) => { const oldSize = targets.size; if (targets.set(node2, value)) { if (targets.size !== oldSize) this.connectionSize++; changed = true; } return targets; }, }); if (changed) this.source = undefined; if (changed && node1 !== node2) { this.linkMap.modifyAt(node2, { ifNew: () => { const targetBuilder = this.context.linkConnectionsContext.builder(); if (!this.isDirected) targetBuilder.set(node1, value); return targetBuilder; }, ifExists: (targets) => { if (!this.isDirected) targets.set(node1, value); return targets; }, }); } return changed; }; this.connect = (node1, node2, value) => { this.checkLock(); return this.connectInternal(node1, node2, value); }; this.connectAll = (connections) => { this.checkLock(); return (Stream.applyFilter(connections, { pred: this.connectInternal, }).count() > 0); }; this.addGraphElement = (element) => { if (ValuedGraphElement.isLink(element)) { return this.connectInternal(element[0], element[1], element[2]); } return this.addNodeInternal(element[0]); }; this.addGraphElements = (elements) => { return (Stream.from(elements).filterPure({ pred: this.addGraphElement }).count() > 0); }; this.modifyAt = (node1, node2, options) => { this.checkLock(); const preConnectionSize = this.connectionSize; let changed = false; let addedOrUpdatedValue; this.linkMap.modifyAt(node1, { ifNew: (none) => { if (undefined === options.ifNew) return none; const newValue = OptLazyOr(options.ifNew, none); if (none === newValue) return none; changed = true; addedOrUpdatedValue = newValue; this.connectionSize++; const builder = this.context.linkMapContext.builder(); builder.set(node2, newValue); return builder; }, ifExists: (valueMap) => { const { ifExists } = options; if (undefined === ifExists) return valueMap; valueMap.modifyAt(node2, { ifNew: (none) => { if (undefined === options.ifNew) return none; const newValue = OptLazyOr(options.ifNew, none); if (none === newValue) return none; changed = true; addedOrUpdatedValue = newValue; this.connectionSize++; return newValue; }, ifExists: (currentValue, remove) => { const newValue = ifExists instanceof Function ? ifExists(currentValue, remove) : ifExists; if (Object.is(newValue, currentValue)) return currentValue; changed = true; if (remove === newValue) { this.connectionSize--; } else { addedOrUpdatedValue = newValue; } return newValue; }, }); return valueMap; }, }); if (!changed) return false; if (this.isDirected) return true; // edge graph, need to update counterpart if (this.connectionSize === preConnectionSize) { // value was updated this.linkMap.modifyAt(node2, { ifNew: () => { const builder = this.context.linkMapContext.builder(); builder.set(node1, addedOrUpdatedValue); return builder; }, ifExists: (valueMap) => { valueMap.set(node1, addedOrUpdatedValue); return valueMap; }, }); return true; } if (this.connectionSize < preConnectionSize) { // value was removed this.linkMap.modifyAt(node2, { ifExists: (valueMap) => { valueMap.removeKey(node1); return valueMap; }, }); return true; } // value was added this.linkMap.modifyAt(node2, { ifNew: () => { const builder = this.context.linkMapContext.builder(); builder.set(node1, addedOrUpdatedValue); return builder; }, ifExists: (valueMap) => { valueMap.set(node1, addedOrUpdatedValue); return valueMap; }, }); return true; }; // prettier-ignore this.disconnectInternal = (node1, node2) => { if (!this.linkMap.context.isValidKey(node1) || !this.linkMap.context.isValidKey(node2)) { return false; } let changed = false; const token = Symbol(); this.linkMap.updateAt(node1, (targets) => { if (token !== targets.removeKey(node2, token)) { this.connectionSize--; changed = true; } return targets; }); if (changed) this.source = undefined; if (changed && node1 !== node2 && !this.isDirected) { this.linkMap.updateAt(node2, (targets) => { targets.removeKey(node1); return targets; }); } return changed; }; // prettier-ignore this.disconnect = (node1, node2) => { this.checkLock(); return this.disconnectInternal(node1, node2); }; // prettier-ignore this.disconnectAll = (connections) => { this.checkLock(); return (Stream.applyFilter(connections, { pred: this.disconnectInternal }).count() > 0); }; this.build = () => { if (undefined !== this.source) return this.source; if (this.isEmpty) return this.context.empty(); const linkMap = this.linkMap .buildMapValues((targets) => targets.build()) .assumeNonEmpty(); return this.context.createNonEmpty(linkMap, this.connectionSize); }; // prettier-ignore this.buildMapValues = (mapFun) => { if (undefined !== this.source) return this.source.mapValues(mapFun); if (this.isEmpty) return this.context.empty(); const linkMap = this.linkMap .buildMapValues((targets, source) => targets.buildMapValues((value, target) => mapFun(value, source, target))) .assumeNonEmpty(); return this.context.createNonEmpty(linkMap, this.connectionSize); }; if (undefined !== source) this.connectionSize = source.connectionSize; } checkLock() { if (this._lock) RimbuError.throwModifiedBuilderWhileLoopingOverItError(); } get linkMap() { if (undefined === this._linkMap) { if (undefined === this.source) { this._linkMap = this.context.linkMapContext.builder(); } else { this._linkMap = this.source.linkMap .mapValues((targets) => targets.toBuilder()) .toBuilder(); } } return this._linkMap; } get isEmpty() { if (this.source) return this.source.isEmpty; return this.linkMap.isEmpty; } get nodeSize() { if (this.source) return this.source.nodeSize; return this.linkMap.size; } } //# sourceMappingURL=builder.mjs.map