@rimbu/graph
Version:
Immutable Graph data structures for TypeScript
217 lines • 7.56 kB
JavaScript
import { TraverseState } from '@rimbu/common';
import { Stream } from '@rimbu/stream';
import { NonEmptyBase } from '@rimbu/collection-types/map-custom';
export class GraphNonEmpty 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))) {
f([node, target], state.nextIndex(), state.halt);
}
}
}
}
stream() {
return this.linkMap.stream().flatMap(([node, targets]) => {
if (!targets.nonEmpty())
return [[node]];
return targets
.stream()
.map((target) => [node, target]);
});
}
get nodeSize() {
return this.linkMap.size;
}
streamNodes() {
return this.linkMap.streamKeys();
}
streamConnections() {
return this.linkMap
.stream()
.flatMap(([node1, targets]) => targets.stream().map((node2) => [node1, node2]));
}
hasNode(node) {
return this.linkMap.hasKey(node);
}
hasConnection(node1, node2) {
const targets = this.linkMap.get(node1);
return targets?.has(node2) ?? false;
}
getConnectionStreamFrom(node1) {
const targets = this.linkMap.get(node1);
if (undefined === targets)
return Stream.empty();
return targets.stream().map((node2) => [node1, node2]);
}
getConnectionStreamTo(node) {
if (this.isDirected) {
return this.linkMap.stream().collect(([source, targets], _, skip) => {
if (!targets?.has(node))
return skip;
return [source, node];
});
}
const targets = this.linkMap.get(node);
if (undefined === targets)
return Stream.empty();
return targets.stream().map((node1) => [node1, node]);
}
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.has(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) {
const newLinkMap = this.linkMap.modifyAt(node1, {
ifNew: this.context.linkConnectionsContext.of(node2),
ifExists: (targets) => targets.add(node2),
});
if (newLinkMap === this.linkMap)
return this;
const newConnectionSize = this.connectionSize + 1;
if (node1 === node2) {
return this.context.createNonEmpty(newLinkMap, newConnectionSize);
}
if (this.isDirected) {
return this.copy(newLinkMap
.modifyAt(node2, {
ifNew: () => this.context.linkConnectionsContext.empty(),
})
.assumeNonEmpty(), newConnectionSize);
}
return this.copy(newLinkMap
.modifyAt(node2, {
ifNew: () => this.context.linkConnectionsContext.of(node1),
ifExists: (targets) => targets.add(node1),
})
.assumeNonEmpty(), newConnectionSize);
}
connectAll(links) {
const builder = this.toBuilder();
builder.connectAll(links);
return builder.build().assumeNonEmpty();
}
disconnect(node1, node2) {
if (!this.linkMap.context.isValidKey(node1) ||
!this.linkMap.context.isValidKey(node2))
return this;
const newLinkMap = this.linkMap.updateAt(node1, (targets) => targets.remove(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.remove(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.has(source))) {
return source;
}
return skip;
});
return this.removeNodes(unconnectedNodes);
}
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: ']' })}`,
});
}
toJSON() {
return {
dataType: this.context.typeTag,
value: this.linkMap
.stream()
.map((entry) => [
entry[0],
entry[1]
.stream()
.map((v) => [v])
.toArray(),
])
.toArray(),
};
}
toBuilder() {
return this.context.createBuilder(this);
}
}
//# sourceMappingURL=non-empty.mjs.map