@rimbu/graph
Version:
Immutable Graph data structures for TypeScript
327 lines • 12.8 kB
JavaScript
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