@rimbu/graph
Version:
Immutable Graph data structures for TypeScript
219 lines • 8.03 kB
JavaScript
import { RimbuError } from '@rimbu/base';
import { Stream } from '@rimbu/stream';
import { GraphElement } from '../../common/index.mjs';
export class GraphBuilder {
constructor(isDirected, context, source) {
this.isDirected = isDirected;
this.context = context;
this.source = source;
this.connectionSize = 0;
this._lock = 0;
// prettier-ignore
this.hasNode = (node) => {
return this.source?.hasNode(node) ?? this.linkMap.hasKey(node);
};
// prettier-ignore
this.hasConnection = (node1, node2) => {
if (undefined !== this.source) {
return this.source.hasConnection(node1, node2);
}
const targets = this.linkMap.get(node1);
return targets?.has(node2) ?? false;
};
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 (undefined === targets)
return false;
this.source = undefined;
if (this.isDirected) {
this.linkMap.forEach(([sourceNode, targets]) => {
if (targets.remove(node)) {
if (sourceNode !== node)
this.connectionSize--;
}
});
}
else {
this.connectionSize -= targets.size;
targets.forEach((target) => this.linkMap.updateAt(target, (values) => {
values.remove(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) => {
let changed = false;
this.linkMap.modifyAt(node1, {
ifNew: () => {
const targetBuilder = this.context.linkConnectionsContext.builder();
targetBuilder.add(node2);
this.connectionSize++;
changed = true;
return targetBuilder;
},
ifExists: (targets) => {
if (targets.add(node2)) {
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.add(node1);
return targetBuilder;
},
ifExists: (targets) => {
if (!this.isDirected)
targets.add(node1);
return targets;
},
});
}
return changed;
};
this.connect = (node1, node2) => {
this.checkLock();
return this.connectInternal(node1, node2);
};
this.connectAll = (connections) => {
this.checkLock();
return (Stream.applyFilter(connections, {
pred: this.connectInternal,
}).count() > 0);
};
this.connectIfNodesExist = (node1, node2) => {
this.checkLock();
let changed = false;
this.linkMap.updateAt(node1, (targets) => {
if (this.linkMap.hasKey(node2) && targets.add(node2)) {
this.connectionSize++;
changed = true;
}
return targets;
});
if (changed && !this.isDirected) {
this.source = undefined;
this.linkMap.updateAt(node2, (targets) => {
targets.add(node1);
return targets;
});
}
return changed;
};
this.addGraphElement = (element) => {
if (GraphElement.isLink(element)) {
return this.connectInternal(element[0], element[1]);
}
return this.addNodeInternal(element[0]);
};
this.addGraphElements = (elements) => {
return (Stream.from(elements).filterPure({ pred: this.addGraphElement }).count() >
0);
};
// prettier-ignore
this.disconnectInternal = (node1, node2) => {
if (!this.linkMap.context.isValidKey(node1) ||
!this.linkMap.context.isValidKey(node2)) {
return false;
}
let changed = false;
this.linkMap.updateAt(node1, (targets) => {
if (targets.remove(node2)) {
this.connectionSize--;
changed = true;
}
return targets;
});
if (changed)
this.source = undefined;
if (changed && node1 !== node2 && !this.isDirected) {
this.linkMap.updateAt(node2, (targets) => {
targets.remove(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);
};
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() {
return this.source?.isEmpty ?? this.linkMap.isEmpty;
}
get nodeSize() {
return this.source?.nodeSize ?? this.linkMap.size;
}
}
//# sourceMappingURL=builder.mjs.map