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