UNPKG

@rimbu/graph

Version:

Immutable Graph data structures for TypeScript

301 lines 11.4 kB
import { NonEmptyBase } from '@rimbu/collection-types/map-custom'; import { OptLazy, OptLazyOr, TraverseState, } from '@rimbu/common'; import { Stream } from '@rimbu/stream'; export class ValuedGraphNonEmpty 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))) { const [targetNode, value] = target; f([node, targetNode, value], state.nextIndex(), state.halt); } } } } stream() { return this.linkMap.stream().flatMap(([node, targets]) => { if (!targets.nonEmpty()) return [[node]]; return targets .stream() .map(([target, value]) => [node, target, value]); }); } get nodeSize() { return this.linkMap.size; } streamNodes() { return this.linkMap.streamKeys(); } streamConnections() { return this.linkMap .stream() .flatMap(([node1, targets]) => targets .stream() .map(([node2, value]) => [node1, node2, value])); } hasNode(node) { return this.linkMap.hasKey(node); } hasConnection(node1, node2) { const targets = this.linkMap.get(node1); return targets?.hasKey(node2) ?? false; } getValue(node1, node2, otherwise) { const targets = this.linkMap.get(node1); if (undefined === targets) return OptLazy(otherwise); return targets.get(node2, otherwise); } getConnectionStreamFrom(node1) { const targets = this.linkMap.get(node1); if (undefined === targets) return Stream.empty(); return targets .stream() .map(([node2, value]) => [node1, node2, value]); } getConnectionStreamTo(node) { if (this.isDirected) { return this.streamConnections().filter(([_, node2]) => node2 === node); } const targets = this.linkMap.get(node); if (undefined === targets) return Stream.empty(); return targets .stream() .map(([node1, value]) => [node1, node, value]); } 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.hasKey(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, value) { const newLinkMap = this.linkMap.modifyAt(node1, { ifNew: this.context.linkConnectionsContext.of([node2, value]), ifExists: (targets) => targets.set(node2, value), }); if (newLinkMap === this.linkMap) return this; const newConnectionSize = this.connectionSize + 1; if (Object.is(node1, node2) || this.isDirected) { return this.context.createNonEmpty(newLinkMap.assumeNonEmpty(), newConnectionSize); } return this.copy(newLinkMap .modifyAt(node2, { ifNew: () => { if (this.isDirected) { return this.context.linkConnectionsContext.empty(); } return this.context.linkConnectionsContext.of([node1, value]); }, }) .assumeNonEmpty(), newConnectionSize); } connectAll(links) { const builder = this.toBuilder(); builder.connectAll(links); return builder.build().assumeNonEmpty(); } modifyAt(node1, node2, options) { let newConnectionSize = this.connectionSize; let addedOrUpdatedValue; const newLinkMap = this.linkMap.modifyAt(node1, { ifNew: (none) => { if (undefined === options.ifNew) return none; const newValue = OptLazyOr(options.ifNew, none); if (none === newValue) return none; addedOrUpdatedValue = newValue; newConnectionSize++; return this.context.linkMapContext.of([node2, newValue]); }, ifExists: (valueMap) => { const { ifExists } = options; if (undefined === ifExists) return valueMap; return valueMap.modifyAt(node2, { ifNew: (none) => { if (undefined === options.ifNew) return none; const newValue = OptLazyOr(options.ifNew, none); if (none === newValue) return none; addedOrUpdatedValue = newValue; newConnectionSize++; return newValue; }, ifExists: (currentValue, remove) => { const newValue = ifExists instanceof Function ? ifExists(currentValue, remove) : ifExists; if (Object.is(newValue, currentValue)) return currentValue; if (remove === newValue) { newConnectionSize--; } else { addedOrUpdatedValue = newValue; } return newValue; }, }); }, }); if (newLinkMap === this.linkMap) return this; if (this.isDirected) { return this.copy(newLinkMap.assumeNonEmpty(), newConnectionSize); } // edge graph, need to update counterpart if (newConnectionSize === this.connectionSize) { // value was updated const newLinkMap2 = newLinkMap.modifyAt(node2, { ifNew: () => this.context.linkMapContext.of([node1, addedOrUpdatedValue]), ifExists: (valueMap) => valueMap.set(node1, addedOrUpdatedValue), }); return this.copy(newLinkMap2.assumeNonEmpty(), newConnectionSize); } if (newConnectionSize < this.connectionSize) { // value was removed const newLinkMap2 = newLinkMap.modifyAt(node2, { ifExists: (valueMap) => valueMap.removeKey(node1), }); return this.copy(newLinkMap2.assumeNonEmpty(), newConnectionSize); } // value was added const newLinkMap2 = newLinkMap.modifyAt(node2, { ifNew: () => this.context.linkMapContext.of([node1, addedOrUpdatedValue]), ifExists: (valueMap) => valueMap.set(node1, addedOrUpdatedValue), }); return this.copy(newLinkMap2.assumeNonEmpty(), newConnectionSize); } disconnect(node1, node2) { if (!this.linkMap.context.isValidKey(node1) || !this.linkMap.context.isValidKey(node2)) return this; const newLinkMap = this.linkMap.updateAt(node1, (targets) => targets.removeKey(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.removeKey(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.hasKey(source))) { return source; } return skip; }); return this.removeNodes(unconnectedNodes); } mapValues(mapFun) { const newLinkMap = this.linkMap.mapValues((targets, node1) => targets.mapValues((value, node2) => mapFun(value, node1, node2))); return this.context.createNonEmpty(newLinkMap, this.connectionSize); } 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: ']', valueToString: ([node2, value]) => `{${node2}: ${value}}`, })}`, }); } toJSON() { return { dataType: this.context.typeTag, value: this.linkMap .stream() .map((entry) => [entry[0], entry[1].toArray()]) .toArray(), }; } toBuilder() { return this.context.createBuilder(this); } } //# sourceMappingURL=non-empty.mjs.map