effect 4.0.0-beta.98 → 4.0.0-beta.99
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -3
- package/dist/Array.js +1 -1
- package/dist/Array.js.map +1 -1
- package/dist/Graph.d.ts +1187 -196
- package/dist/Graph.d.ts.map +1 -1
- package/dist/Graph.js +1031 -283
- package/dist/Graph.js.map +1 -1
- package/dist/Iterable.d.ts.map +1 -1
- package/dist/Iterable.js +41 -1
- package/dist/Iterable.js.map +1 -1
- package/dist/Schema.js +1 -1
- package/dist/Schema.js.map +1 -1
- package/dist/internal/effect.js +9 -3
- package/dist/internal/effect.js.map +1 -1
- package/dist/unstable/ai/McpSchema.d.ts +128 -128
- package/dist/unstable/ai/McpSchema.d.ts.map +1 -1
- package/dist/unstable/ai/Tool.d.ts.map +1 -1
- package/dist/unstable/ai/Tool.js +11 -13
- package/dist/unstable/ai/Tool.js.map +1 -1
- package/dist/unstable/cli/CliConfig.d.ts +76 -0
- package/dist/unstable/cli/CliConfig.d.ts.map +1 -0
- package/dist/unstable/cli/CliConfig.js +63 -0
- package/dist/unstable/cli/CliConfig.js.map +1 -0
- package/dist/unstable/cli/CliError.d.ts.map +1 -1
- package/dist/unstable/cli/CliError.js +3 -0
- package/dist/unstable/cli/CliError.js.map +1 -1
- package/dist/unstable/cli/Command.d.ts +36 -1
- package/dist/unstable/cli/Command.d.ts.map +1 -1
- package/dist/unstable/cli/Command.js +61 -4
- package/dist/unstable/cli/Command.js.map +1 -1
- package/dist/unstable/cli/GlobalFlag.d.ts +23 -2
- package/dist/unstable/cli/GlobalFlag.d.ts.map +1 -1
- package/dist/unstable/cli/GlobalFlag.js +20 -4
- package/dist/unstable/cli/GlobalFlag.js.map +1 -1
- package/dist/unstable/cli/Param.js +6 -2
- package/dist/unstable/cli/Param.js.map +1 -1
- package/dist/unstable/cli/index.d.ts +4 -0
- package/dist/unstable/cli/index.d.ts.map +1 -1
- package/dist/unstable/cli/index.js +4 -0
- package/dist/unstable/cli/index.js.map +1 -1
- package/dist/unstable/cli/internal/ansi.d.ts.map +1 -1
- package/dist/unstable/cli/internal/ansi.js +2 -0
- package/dist/unstable/cli/internal/ansi.js.map +1 -1
- package/dist/unstable/cli/internal/parser.js +11 -2
- package/dist/unstable/cli/internal/parser.js.map +1 -1
- package/dist/unstable/cli/internal/wizard.d.ts +13 -0
- package/dist/unstable/cli/internal/wizard.d.ts.map +1 -0
- package/dist/unstable/cli/internal/wizard.js +212 -0
- package/dist/unstable/cli/internal/wizard.js.map +1 -0
- package/dist/unstable/cluster/RunnerStorage.js +1 -1
- package/dist/unstable/cluster/RunnerStorage.js.map +1 -1
- package/dist/unstable/http/Multipart.d.ts.map +1 -1
- package/dist/unstable/http/Multipart.js +3 -1
- package/dist/unstable/http/Multipart.js.map +1 -1
- package/dist/unstable/httpapi/HttpApiBuilder.d.ts +1 -1
- package/dist/unstable/httpapi/HttpApiBuilder.d.ts.map +1 -1
- package/dist/unstable/persistence/Redis.d.ts.map +1 -1
- package/dist/unstable/persistence/Redis.js +5 -3
- package/dist/unstable/persistence/Redis.js.map +1 -1
- package/dist/unstable/sql/SqlClient.d.ts +1 -2
- package/dist/unstable/sql/SqlClient.d.ts.map +1 -1
- package/dist/unstable/sql/SqlClient.js.map +1 -1
- package/package.json +3 -3
- package/src/Array.ts +1 -1
- package/src/Graph.ts +2967 -457
- package/src/Iterable.ts +32 -2
- package/src/Schema.ts +1 -1
- package/src/internal/effect.ts +9 -3
- package/src/unstable/ai/Tool.ts +13 -16
- package/src/unstable/cli/CliConfig.ts +85 -0
- package/src/unstable/cli/CliError.ts +3 -0
- package/src/unstable/cli/Command.ts +83 -7
- package/src/unstable/cli/GlobalFlag.ts +33 -5
- package/src/unstable/cli/Param.ts +15 -3
- package/src/unstable/cli/index.ts +5 -0
- package/src/unstable/cli/internal/ansi.ts +3 -0
- package/src/unstable/cli/internal/parser.ts +14 -2
- package/src/unstable/cli/internal/wizard.ts +283 -0
- package/src/unstable/cluster/RunnerStorage.ts +1 -1
- package/src/unstable/http/Multipart.ts +3 -1
- package/src/unstable/httpapi/HttpApiBuilder.ts +1 -1
- package/src/unstable/persistence/Redis.ts +9 -4
- package/src/unstable/sql/SqlClient.ts +1 -2
package/dist/Graph.js
CHANGED
|
@@ -13,6 +13,7 @@ import * as Equal from "./Equal.js";
|
|
|
13
13
|
import { dual } from "./Function.js";
|
|
14
14
|
import * as Hash from "./Hash.js";
|
|
15
15
|
import { NodeInspectSymbol } from "./Inspectable.js";
|
|
16
|
+
import * as MutableHashMap from "./MutableHashMap.js";
|
|
16
17
|
import * as Option from "./Option.js";
|
|
17
18
|
import { pipeArguments } from "./Pipeable.js";
|
|
18
19
|
import { hasProperty } from "./Predicate.js";
|
|
@@ -33,12 +34,29 @@ const TypeId = "~effect/collections/Graph";
|
|
|
33
34
|
* @since 3.18.0
|
|
34
35
|
*/
|
|
35
36
|
export class Edge extends Data.Class {}
|
|
37
|
+
/** @internal */
|
|
38
|
+
const graphImpl = graph => graph;
|
|
39
|
+
/** @internal */
|
|
40
|
+
const cloneAdjacency = adjacency => {
|
|
41
|
+
const cloned = new Map();
|
|
42
|
+
for (const [nodeIndex, edges] of adjacency) {
|
|
43
|
+
cloned.set(nodeIndex, [...edges]);
|
|
44
|
+
}
|
|
45
|
+
return cloned;
|
|
46
|
+
};
|
|
36
47
|
// =============================================================================
|
|
37
48
|
// Proto Objects
|
|
38
49
|
// =============================================================================
|
|
39
50
|
/** @internal */
|
|
51
|
+
const edgeEquals = (type, self, that) => (type === "directed" ? self.source === that.source && self.target === that.target : self.source === that.source && self.target === that.target || self.source === that.target && self.target === that.source) && Equal.equals(self.data, that.data);
|
|
52
|
+
/** @internal */
|
|
53
|
+
const edgeHash = (type, edge) => type === "directed" ? Hash.hash(edge) : Hash.optimize(Hash.hash(edge.data) ^ Hash.hash(edge.source) + Hash.hash(edge.target));
|
|
54
|
+
/** @internal */
|
|
40
55
|
const ProtoGraph = {
|
|
41
|
-
[TypeId]:
|
|
56
|
+
[TypeId]: {
|
|
57
|
+
_N: _ => _,
|
|
58
|
+
_E: _ => _
|
|
59
|
+
},
|
|
42
60
|
[Symbol.iterator]() {
|
|
43
61
|
return this.nodes[Symbol.iterator]();
|
|
44
62
|
},
|
|
@@ -47,26 +65,27 @@ const ProtoGraph = {
|
|
|
47
65
|
},
|
|
48
66
|
[Equal.symbol](that) {
|
|
49
67
|
if (isGraph(that)) {
|
|
50
|
-
|
|
68
|
+
const thatImpl = graphImpl(that);
|
|
69
|
+
if (this.nodes.size !== thatImpl.nodes.size || this.edges.size !== thatImpl.edges.size || this.nextNodeIndex !== thatImpl.nextNodeIndex || this.nextEdgeIndex !== thatImpl.nextEdgeIndex || this.type !== thatImpl.type) {
|
|
51
70
|
return false;
|
|
52
71
|
}
|
|
53
72
|
// Compare nodes
|
|
54
73
|
for (const [nodeIndex, nodeData] of this.nodes) {
|
|
55
|
-
if (!
|
|
74
|
+
if (!thatImpl.nodes.has(nodeIndex)) {
|
|
56
75
|
return false;
|
|
57
76
|
}
|
|
58
|
-
const otherNodeData =
|
|
77
|
+
const otherNodeData = thatImpl.nodes.get(nodeIndex);
|
|
59
78
|
if (!Equal.equals(nodeData, otherNodeData)) {
|
|
60
79
|
return false;
|
|
61
80
|
}
|
|
62
81
|
}
|
|
63
82
|
// Compare edges
|
|
64
83
|
for (const [edgeIndex, edgeData] of this.edges) {
|
|
65
|
-
if (!
|
|
84
|
+
if (!thatImpl.edges.has(edgeIndex)) {
|
|
66
85
|
return false;
|
|
67
86
|
}
|
|
68
|
-
const otherEdge =
|
|
69
|
-
if (!
|
|
87
|
+
const otherEdge = thatImpl.edges.get(edgeIndex);
|
|
88
|
+
if (!edgeEquals(this.type, edgeData, otherEdge)) {
|
|
70
89
|
return false;
|
|
71
90
|
}
|
|
72
91
|
}
|
|
@@ -79,11 +98,13 @@ const ProtoGraph = {
|
|
|
79
98
|
hash = hash ^ Hash.string(this.type);
|
|
80
99
|
hash = hash ^ Hash.number(this.nodes.size);
|
|
81
100
|
hash = hash ^ Hash.number(this.edges.size);
|
|
101
|
+
hash = hash ^ Hash.number(this.nextNodeIndex);
|
|
102
|
+
hash = hash ^ Hash.number(this.nextEdgeIndex);
|
|
82
103
|
for (const [nodeIndex, nodeData] of this.nodes) {
|
|
83
104
|
hash = hash ^ Hash.hash(nodeIndex) + Hash.hash(nodeData);
|
|
84
105
|
}
|
|
85
106
|
for (const [edgeIndex, edgeData] of this.edges) {
|
|
86
|
-
hash = hash ^ Hash.hash(edgeIndex) +
|
|
107
|
+
hash = hash ^ Hash.hash(edgeIndex) + edgeHash(this.type, edgeData);
|
|
87
108
|
}
|
|
88
109
|
return hash;
|
|
89
110
|
},
|
|
@@ -124,12 +145,20 @@ export class GraphError extends /*#__PURE__*/Data.TaggedError("GraphError") {}
|
|
|
124
145
|
const missingNode = node => new GraphError({
|
|
125
146
|
message: `Node ${node} does not exist`
|
|
126
147
|
});
|
|
148
|
+
/** @internal */
|
|
149
|
+
function assertMutable(graph) {
|
|
150
|
+
if (!graph.mutable) {
|
|
151
|
+
throw new GraphError({
|
|
152
|
+
message: "Graph is not mutable"
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
}
|
|
127
156
|
// =============================================================================
|
|
128
157
|
// Constructors
|
|
129
158
|
// =============================================================================
|
|
130
159
|
/**
|
|
131
160
|
* Returns `true` if a value has the graph runtime type identifier, narrowing
|
|
132
|
-
* it to
|
|
161
|
+
* it to an immutable or mutable graph.
|
|
133
162
|
*
|
|
134
163
|
* **When to use**
|
|
135
164
|
*
|
|
@@ -138,36 +167,42 @@ const missingNode = node => new GraphError({
|
|
|
138
167
|
* **Gotchas**
|
|
139
168
|
*
|
|
140
169
|
* This guard checks the shared graph runtime type identifier and does not
|
|
141
|
-
* distinguish immutable graphs from mutable graphs
|
|
170
|
+
* distinguish immutable graphs from mutable graphs or directed graphs from
|
|
171
|
+
* undirected graphs.
|
|
142
172
|
*
|
|
143
173
|
* @category guards
|
|
144
174
|
* @since 4.0.0
|
|
145
175
|
*/
|
|
146
176
|
export const isGraph = u => hasProperty(u, TypeId);
|
|
147
177
|
/**
|
|
148
|
-
* Creates a
|
|
178
|
+
* Creates a graph constructor for the specified graph kind.
|
|
149
179
|
*
|
|
150
|
-
* **
|
|
180
|
+
* **When to use**
|
|
181
|
+
*
|
|
182
|
+
* Use when the graph kind is selected dynamically. Prefer `directed` or
|
|
183
|
+
* `undirected` when the kind is known statically.
|
|
184
|
+
*
|
|
185
|
+
* **Example** (Constructing by kind)
|
|
151
186
|
*
|
|
152
187
|
* ```ts
|
|
153
188
|
* import { Graph } from "effect"
|
|
154
189
|
*
|
|
155
|
-
*
|
|
156
|
-
* const graph =
|
|
157
|
-
*
|
|
158
|
-
* const b = Graph.addNode(mutable, "B")
|
|
159
|
-
* const c = Graph.addNode(mutable, "C")
|
|
160
|
-
* Graph.addEdge(mutable, a, b, "A->B")
|
|
161
|
-
* Graph.addEdge(mutable, b, c, "B->C")
|
|
190
|
+
* const makeGraph = Graph.make("directed")
|
|
191
|
+
* const graph = makeGraph<string, number>((mutable) => {
|
|
192
|
+
* Graph.addNode(mutable, "A")
|
|
162
193
|
* })
|
|
194
|
+
*
|
|
195
|
+
* console.log(graph.type) // "directed"
|
|
163
196
|
* ```
|
|
164
197
|
*
|
|
198
|
+
* @see {@link directed} for constructing a directed graph directly
|
|
199
|
+
* @see {@link undirected} for constructing an undirected graph directly
|
|
165
200
|
* @category constructors
|
|
166
|
-
* @since
|
|
201
|
+
* @since 4.0.0
|
|
167
202
|
*/
|
|
168
|
-
export const
|
|
203
|
+
export const make = type => mutate => {
|
|
169
204
|
const graph = Object.create(ProtoGraph);
|
|
170
|
-
graph.type =
|
|
205
|
+
graph.type = type;
|
|
171
206
|
graph.nodes = new Map();
|
|
172
207
|
graph.edges = new Map();
|
|
173
208
|
graph.adjacency = new Map();
|
|
@@ -175,14 +210,36 @@ export const directed = mutate => {
|
|
|
175
210
|
graph.nextNodeIndex = 0;
|
|
176
211
|
graph.nextEdgeIndex = 0;
|
|
177
212
|
graph.acyclic = Option.some(true);
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
mutate(mutable);
|
|
182
|
-
return endMutation(mutable);
|
|
213
|
+
if (mutate === undefined) {
|
|
214
|
+
graph.mutable = false;
|
|
215
|
+
return graph;
|
|
183
216
|
}
|
|
184
|
-
|
|
217
|
+
graph.mutable = true;
|
|
218
|
+
const mutable = Equal.byReferenceUnsafe(graph);
|
|
219
|
+
return mutateScoped(mutable, mutate);
|
|
185
220
|
};
|
|
221
|
+
/**
|
|
222
|
+
* Creates a directed graph, optionally with initial mutations.
|
|
223
|
+
*
|
|
224
|
+
* **Example** (Creating a directed graph)
|
|
225
|
+
*
|
|
226
|
+
* ```ts
|
|
227
|
+
* import { Graph } from "effect"
|
|
228
|
+
*
|
|
229
|
+
* // Directed graph with initial nodes and edges
|
|
230
|
+
* const graph = Graph.directed<string, string>((mutable) => {
|
|
231
|
+
* const a = Graph.addNode(mutable, "A")
|
|
232
|
+
* const b = Graph.addNode(mutable, "B")
|
|
233
|
+
* const c = Graph.addNode(mutable, "C")
|
|
234
|
+
* Graph.addEdge(mutable, a, b, "A->B")
|
|
235
|
+
* Graph.addEdge(mutable, b, c, "B->C")
|
|
236
|
+
* })
|
|
237
|
+
* ```
|
|
238
|
+
*
|
|
239
|
+
* @category constructors
|
|
240
|
+
* @since 3.18.0
|
|
241
|
+
*/
|
|
242
|
+
export const directed = /*#__PURE__*/make("directed");
|
|
186
243
|
/**
|
|
187
244
|
* Creates an undirected graph, optionally with initial mutations.
|
|
188
245
|
*
|
|
@@ -204,24 +261,7 @@ export const directed = mutate => {
|
|
|
204
261
|
* @category constructors
|
|
205
262
|
* @since 3.18.0
|
|
206
263
|
*/
|
|
207
|
-
export const undirected =
|
|
208
|
-
const graph = Object.create(ProtoGraph);
|
|
209
|
-
graph.type = "undirected";
|
|
210
|
-
graph.nodes = new Map();
|
|
211
|
-
graph.edges = new Map();
|
|
212
|
-
graph.adjacency = new Map();
|
|
213
|
-
graph.reverseAdjacency = new Map();
|
|
214
|
-
graph.nextNodeIndex = 0;
|
|
215
|
-
graph.nextEdgeIndex = 0;
|
|
216
|
-
graph.acyclic = Option.some(true);
|
|
217
|
-
graph.mutable = false;
|
|
218
|
-
if (mutate) {
|
|
219
|
-
const mutable = beginMutation(graph);
|
|
220
|
-
mutate(mutable);
|
|
221
|
-
return endMutation(mutable);
|
|
222
|
-
}
|
|
223
|
-
return graph;
|
|
224
|
-
};
|
|
264
|
+
export const undirected = /*#__PURE__*/make("undirected");
|
|
225
265
|
// =============================================================================
|
|
226
266
|
// Scoped Mutable API
|
|
227
267
|
// =============================================================================
|
|
@@ -242,30 +282,29 @@ export const undirected = mutate => {
|
|
|
242
282
|
* @since 3.18.0
|
|
243
283
|
*/
|
|
244
284
|
export const beginMutation = graph => {
|
|
245
|
-
|
|
246
|
-
const adjacency =
|
|
247
|
-
const reverseAdjacency =
|
|
248
|
-
for (const [nodeIndex, edges] of graph.adjacency) {
|
|
249
|
-
adjacency.set(nodeIndex, [...edges]);
|
|
250
|
-
}
|
|
251
|
-
for (const [nodeIndex, edges] of graph.reverseAdjacency) {
|
|
252
|
-
reverseAdjacency.set(nodeIndex, [...edges]);
|
|
253
|
-
}
|
|
285
|
+
const source = graphImpl(graph);
|
|
286
|
+
const adjacency = cloneAdjacency(source.adjacency);
|
|
287
|
+
const reverseAdjacency = cloneAdjacency(source.reverseAdjacency);
|
|
254
288
|
const mutable = Object.create(ProtoGraph);
|
|
255
|
-
mutable.type =
|
|
256
|
-
mutable.nodes = new Map(
|
|
257
|
-
mutable.edges = new Map(
|
|
289
|
+
mutable.type = source.type;
|
|
290
|
+
mutable.nodes = new Map(source.nodes);
|
|
291
|
+
mutable.edges = new Map(source.edges);
|
|
258
292
|
mutable.adjacency = adjacency;
|
|
259
293
|
mutable.reverseAdjacency = reverseAdjacency;
|
|
260
|
-
mutable.nextNodeIndex =
|
|
261
|
-
mutable.nextEdgeIndex =
|
|
262
|
-
mutable.acyclic =
|
|
294
|
+
mutable.nextNodeIndex = source.nextNodeIndex;
|
|
295
|
+
mutable.nextEdgeIndex = source.nextEdgeIndex;
|
|
296
|
+
mutable.acyclic = source.acyclic;
|
|
263
297
|
mutable.mutable = true;
|
|
264
|
-
return mutable;
|
|
298
|
+
return Equal.byReferenceUnsafe(mutable);
|
|
265
299
|
};
|
|
266
300
|
/**
|
|
267
301
|
* Converts a mutable graph back to an immutable graph, ending the mutation scope.
|
|
268
302
|
*
|
|
303
|
+
* **Details**
|
|
304
|
+
*
|
|
305
|
+
* Finalizes the mutable handle. Later public mutation operations on that handle
|
|
306
|
+
* fail with a `GraphError`.
|
|
307
|
+
*
|
|
269
308
|
* **Example** (Ending a mutation scope)
|
|
270
309
|
*
|
|
271
310
|
* ```ts
|
|
@@ -281,16 +320,29 @@ export const beginMutation = graph => {
|
|
|
281
320
|
* @since 3.18.0
|
|
282
321
|
*/
|
|
283
322
|
export const endMutation = mutable => {
|
|
323
|
+
assertMutable(mutable);
|
|
324
|
+
const source = graphImpl(mutable);
|
|
284
325
|
const graph = Object.create(ProtoGraph);
|
|
285
|
-
graph.type =
|
|
286
|
-
graph.nodes = new Map(
|
|
287
|
-
graph.edges = new Map(
|
|
288
|
-
graph.adjacency =
|
|
289
|
-
graph.reverseAdjacency =
|
|
290
|
-
graph.nextNodeIndex =
|
|
291
|
-
graph.nextEdgeIndex =
|
|
292
|
-
graph.acyclic =
|
|
326
|
+
graph.type = source.type;
|
|
327
|
+
graph.nodes = new Map(source.nodes);
|
|
328
|
+
graph.edges = new Map(source.edges);
|
|
329
|
+
graph.adjacency = cloneAdjacency(source.adjacency);
|
|
330
|
+
graph.reverseAdjacency = cloneAdjacency(source.reverseAdjacency);
|
|
331
|
+
graph.nextNodeIndex = source.nextNodeIndex;
|
|
332
|
+
graph.nextEdgeIndex = source.nextEdgeIndex;
|
|
333
|
+
graph.acyclic = source.acyclic;
|
|
293
334
|
graph.mutable = false;
|
|
335
|
+
source.mutable = false;
|
|
336
|
+
return graph;
|
|
337
|
+
};
|
|
338
|
+
/** @internal */
|
|
339
|
+
const mutateScoped = (mutable, f) => {
|
|
340
|
+
let graph;
|
|
341
|
+
try {
|
|
342
|
+
f(mutable);
|
|
343
|
+
} finally {
|
|
344
|
+
graph = endMutation(mutable);
|
|
345
|
+
}
|
|
294
346
|
return graph;
|
|
295
347
|
};
|
|
296
348
|
/**
|
|
@@ -317,8 +369,529 @@ export const endMutation = mutable => {
|
|
|
317
369
|
*/
|
|
318
370
|
export const mutate = /*#__PURE__*/dual(2, (graph, f) => {
|
|
319
371
|
const mutable = beginMutation(graph);
|
|
320
|
-
|
|
321
|
-
|
|
372
|
+
return mutateScoped(mutable, f);
|
|
373
|
+
});
|
|
374
|
+
/** @internal */
|
|
375
|
+
class EdgeIdentity {
|
|
376
|
+
type;
|
|
377
|
+
source;
|
|
378
|
+
target;
|
|
379
|
+
identity;
|
|
380
|
+
constructor(type, source, target, identity) {
|
|
381
|
+
this.type = type;
|
|
382
|
+
this.source = source;
|
|
383
|
+
this.target = target;
|
|
384
|
+
this.identity = identity;
|
|
385
|
+
}
|
|
386
|
+
[Equal.symbol](that) {
|
|
387
|
+
if (!(that instanceof EdgeIdentity) || this.type !== that.type || !Equal.equals(this.identity, that.identity)) {
|
|
388
|
+
return false;
|
|
389
|
+
}
|
|
390
|
+
if (this.type === "directed") {
|
|
391
|
+
return Equal.equals(this.source, that.source) && Equal.equals(this.target, that.target);
|
|
392
|
+
}
|
|
393
|
+
return Equal.equals(this.source, that.source) && Equal.equals(this.target, that.target) || Equal.equals(this.source, that.target) && Equal.equals(this.target, that.source);
|
|
394
|
+
}
|
|
395
|
+
[Hash.symbol]() {
|
|
396
|
+
const hash = Hash.hash(this.identity);
|
|
397
|
+
return this.type === "directed" ? Hash.combine(Hash.hash(this.target))(Hash.combine(Hash.hash(this.source))(hash)) : Hash.optimize(hash ^ Hash.hash(this.source) + Hash.hash(this.target));
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
/** @internal */
|
|
401
|
+
const buildNodeMaps = (graph, identity) => {
|
|
402
|
+
const impl = graphImpl(graph);
|
|
403
|
+
const byIdentity = MutableHashMap.empty();
|
|
404
|
+
const byIndex = new Map();
|
|
405
|
+
for (const [index, data] of impl.nodes) {
|
|
406
|
+
const nodeIdentity = identity(data);
|
|
407
|
+
MutableHashMap.set(byIdentity, nodeIdentity, data);
|
|
408
|
+
byIndex.set(index, nodeIdentity);
|
|
409
|
+
}
|
|
410
|
+
return {
|
|
411
|
+
byIdentity,
|
|
412
|
+
byIndex
|
|
413
|
+
};
|
|
414
|
+
};
|
|
415
|
+
/** @internal */
|
|
416
|
+
const nodeIdentityAt = (maps, index) => maps.byIndex.get(index);
|
|
417
|
+
/** @internal */
|
|
418
|
+
const buildEdgeMap = (graph, nodeMaps, identity) => {
|
|
419
|
+
const impl = graphImpl(graph);
|
|
420
|
+
const edges = MutableHashMap.empty();
|
|
421
|
+
for (const edge of impl.edges.values()) {
|
|
422
|
+
const sourceIdentity = nodeIdentityAt(nodeMaps, edge.source);
|
|
423
|
+
const targetIdentity = nodeIdentityAt(nodeMaps, edge.target);
|
|
424
|
+
MutableHashMap.set(edges, new EdgeIdentity(graph.type, sourceIdentity, targetIdentity, identity(edge.data)), edge.data);
|
|
425
|
+
}
|
|
426
|
+
return edges;
|
|
427
|
+
};
|
|
428
|
+
/** @internal */
|
|
429
|
+
const addNodesByIdentity = (mutable, nodes) => {
|
|
430
|
+
const indexByIdentity = MutableHashMap.empty();
|
|
431
|
+
for (const [identity, data] of nodes) {
|
|
432
|
+
MutableHashMap.set(indexByIdentity, identity, addNode(mutable, data));
|
|
433
|
+
}
|
|
434
|
+
return indexByIdentity;
|
|
435
|
+
};
|
|
436
|
+
/** @internal */
|
|
437
|
+
const addEdgeByIdentity = (mutable, indexByIdentity, identity, data) => {
|
|
438
|
+
const sourceIndex = Option.getOrUndefined(MutableHashMap.get(indexByIdentity, identity.source));
|
|
439
|
+
const targetIndex = Option.getOrUndefined(MutableHashMap.get(indexByIdentity, identity.target));
|
|
440
|
+
if (sourceIndex !== undefined && targetIndex !== undefined) {
|
|
441
|
+
addEdge(mutable, sourceIndex, targetIndex, data);
|
|
442
|
+
}
|
|
443
|
+
};
|
|
444
|
+
/** @internal */
|
|
445
|
+
const assertSameKind = (self, that) => {
|
|
446
|
+
if (self.type !== that.type) {
|
|
447
|
+
throw new GraphError({
|
|
448
|
+
message: `Cannot combine ${self.type} and ${that.type} graphs`
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
};
|
|
452
|
+
/**
|
|
453
|
+
* Composes two graphs, merging nodes by identity.
|
|
454
|
+
*
|
|
455
|
+
* **Details**
|
|
456
|
+
*
|
|
457
|
+
* Nodes and edges present in both graphs use data from `that`. The result has
|
|
458
|
+
* the same graph kind as `self`. Throws a `GraphError` when the graph kinds do
|
|
459
|
+
* not match. `nodeIdentity` and `edgeIdentity` default to the complete node and
|
|
460
|
+
* edge data. Edge identity also includes the endpoint identities.
|
|
461
|
+
*
|
|
462
|
+
* `G1 ∪ G2 = {V1 ∪ V2, E1 ∪ E2}`
|
|
463
|
+
*
|
|
464
|
+
* **Gotchas**
|
|
465
|
+
*
|
|
466
|
+
* Nodes with equal identities in one input graph are coalesced. The last node
|
|
467
|
+
* supplies the data, and redirected edges can collapse or become self-loops.
|
|
468
|
+
* Parallel edges with equal identities are also coalesced, with the last edge
|
|
469
|
+
* supplying the data.
|
|
470
|
+
*
|
|
471
|
+
* **Example** (Combining graphs)
|
|
472
|
+
*
|
|
473
|
+
* ```ts
|
|
474
|
+
* import { Graph } from "effect"
|
|
475
|
+
*
|
|
476
|
+
* const left = Graph.directed<{ id: string }, string>((mutable) => {
|
|
477
|
+
* const a = Graph.addNode(mutable, { id: "A" })
|
|
478
|
+
* const b = Graph.addNode(mutable, { id: "B" })
|
|
479
|
+
* Graph.addEdge(mutable, a, b, "A-B")
|
|
480
|
+
* })
|
|
481
|
+
*
|
|
482
|
+
* const right = Graph.directed<{ id: string }, string>((mutable) => {
|
|
483
|
+
* const b = Graph.addNode(mutable, { id: "B" })
|
|
484
|
+
* const c = Graph.addNode(mutable, { id: "C" })
|
|
485
|
+
* Graph.addEdge(mutable, b, c, "B-C")
|
|
486
|
+
* })
|
|
487
|
+
*
|
|
488
|
+
* const result = Graph.compose(left, right, {
|
|
489
|
+
* nodeIdentity: (node) => node.id
|
|
490
|
+
* })
|
|
491
|
+
*
|
|
492
|
+
* console.log(Graph.nodeCount(result)) // 3
|
|
493
|
+
* console.log(Graph.edgeCount(result)) // 2
|
|
494
|
+
* ```
|
|
495
|
+
*
|
|
496
|
+
* @category set operations
|
|
497
|
+
* @since 4.0.0
|
|
498
|
+
*/
|
|
499
|
+
export const compose = /*#__PURE__*/dual(args => isGraph(args[0]) && isGraph(args[1]), (self, that, options) => {
|
|
500
|
+
assertSameKind(self, that);
|
|
501
|
+
const getNodeIdentity = options?.nodeIdentity ?? (node => node);
|
|
502
|
+
const getEdgeIdentity = options?.edgeIdentity ?? (edge => edge);
|
|
503
|
+
const selfMaps = buildNodeMaps(self, getNodeIdentity);
|
|
504
|
+
const thatMaps = buildNodeMaps(that, getNodeIdentity);
|
|
505
|
+
const nodes = MutableHashMap.empty();
|
|
506
|
+
const edges = buildEdgeMap(self, selfMaps, getEdgeIdentity);
|
|
507
|
+
for (const [identity, data] of selfMaps.byIdentity) {
|
|
508
|
+
MutableHashMap.set(nodes, identity, data);
|
|
509
|
+
}
|
|
510
|
+
for (const [identity, data] of thatMaps.byIdentity) {
|
|
511
|
+
MutableHashMap.set(nodes, identity, data);
|
|
512
|
+
}
|
|
513
|
+
for (const [identity, data] of buildEdgeMap(that, thatMaps, getEdgeIdentity)) {
|
|
514
|
+
MutableHashMap.set(edges, identity, data);
|
|
515
|
+
}
|
|
516
|
+
return make(self.type)(mutable => {
|
|
517
|
+
const indexByIdentity = addNodesByIdentity(mutable, nodes);
|
|
518
|
+
for (const [identity, data] of edges) {
|
|
519
|
+
addEdgeByIdentity(mutable, indexByIdentity, identity, data);
|
|
520
|
+
}
|
|
521
|
+
});
|
|
522
|
+
});
|
|
523
|
+
/**
|
|
524
|
+
* Returns the intersection of two graphs, matching nodes by identity.
|
|
525
|
+
*
|
|
526
|
+
* **Details**
|
|
527
|
+
*
|
|
528
|
+
* Node data comes from `self`, and edge data comes from `that`. The result has
|
|
529
|
+
* the same graph kind as `self`. Throws a `GraphError` when the graph kinds do
|
|
530
|
+
* not match. `nodeIdentity` and `edgeIdentity` default to the complete node and
|
|
531
|
+
* edge data. Edge identity also includes the endpoint identities.
|
|
532
|
+
*
|
|
533
|
+
* `G1 ∩ G2 = {V1 ∩ V2, E1 ∩ E2}`
|
|
534
|
+
*
|
|
535
|
+
* **Gotchas**
|
|
536
|
+
*
|
|
537
|
+
* Nodes with equal identities in one input graph are coalesced. The last node
|
|
538
|
+
* supplies the data, and redirected edges can collapse or become self-loops.
|
|
539
|
+
* The result contains at most one edge for each shared edge identity.
|
|
540
|
+
*
|
|
541
|
+
* **Example** (Finding shared structure)
|
|
542
|
+
*
|
|
543
|
+
* ```ts
|
|
544
|
+
* import { Graph } from "effect"
|
|
545
|
+
*
|
|
546
|
+
* const left = Graph.directed<string, string>((mutable) => {
|
|
547
|
+
* const a = Graph.addNode(mutable, "A")
|
|
548
|
+
* const b = Graph.addNode(mutable, "B")
|
|
549
|
+
* Graph.addEdge(mutable, a, b, "shared")
|
|
550
|
+
* })
|
|
551
|
+
*
|
|
552
|
+
* const right = Graph.directed<string, string>((mutable) => {
|
|
553
|
+
* const a = Graph.addNode(mutable, "A")
|
|
554
|
+
* const b = Graph.addNode(mutable, "B")
|
|
555
|
+
* Graph.addEdge(mutable, a, b, "shared")
|
|
556
|
+
* })
|
|
557
|
+
*
|
|
558
|
+
* const result = Graph.intersection(left, right)
|
|
559
|
+
*
|
|
560
|
+
* console.log(Graph.nodeCount(result)) // 2
|
|
561
|
+
* console.log(Graph.edgeCount(result)) // 1
|
|
562
|
+
* ```
|
|
563
|
+
*
|
|
564
|
+
* @category set operations
|
|
565
|
+
* @since 4.0.0
|
|
566
|
+
*/
|
|
567
|
+
export const intersection = /*#__PURE__*/dual(args => isGraph(args[0]) && isGraph(args[1]), (self, that, options) => {
|
|
568
|
+
assertSameKind(self, that);
|
|
569
|
+
const thatImpl = graphImpl(that);
|
|
570
|
+
const getNodeIdentity = options?.nodeIdentity ?? (node => node);
|
|
571
|
+
const getEdgeIdentity = options?.edgeIdentity ?? (edge => edge);
|
|
572
|
+
const selfMaps = buildNodeMaps(self, getNodeIdentity);
|
|
573
|
+
const thatMaps = buildNodeMaps(that, getNodeIdentity);
|
|
574
|
+
const nodes = MutableHashMap.empty();
|
|
575
|
+
const selfEdges = buildEdgeMap(self, selfMaps, getEdgeIdentity);
|
|
576
|
+
const thatEdges = MutableHashMap.empty();
|
|
577
|
+
for (const [identity, data] of selfMaps.byIdentity) {
|
|
578
|
+
if (MutableHashMap.has(thatMaps.byIdentity, identity)) {
|
|
579
|
+
MutableHashMap.set(nodes, identity, data);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
for (const edge of thatImpl.edges.values()) {
|
|
583
|
+
const sourceIdentity = nodeIdentityAt(thatMaps, edge.source);
|
|
584
|
+
const targetIdentity = nodeIdentityAt(thatMaps, edge.target);
|
|
585
|
+
if (MutableHashMap.has(nodes, sourceIdentity) && MutableHashMap.has(nodes, targetIdentity)) {
|
|
586
|
+
const edgeIdentity = new EdgeIdentity(that.type, sourceIdentity, targetIdentity, getEdgeIdentity(edge.data));
|
|
587
|
+
MutableHashMap.set(thatEdges, edgeIdentity, edge.data);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
return make(self.type)(mutable => {
|
|
591
|
+
const indexByIdentity = addNodesByIdentity(mutable, nodes);
|
|
592
|
+
for (const [identity, data] of thatEdges) {
|
|
593
|
+
if (MutableHashMap.has(selfEdges, identity)) {
|
|
594
|
+
addEdgeByIdentity(mutable, indexByIdentity, identity, data);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
});
|
|
598
|
+
});
|
|
599
|
+
/**
|
|
600
|
+
* Returns `self` without edges also present in `that`.
|
|
601
|
+
*
|
|
602
|
+
* **Details**
|
|
603
|
+
*
|
|
604
|
+
* All nodes from `self` are preserved. Edges are matched by endpoint and edge
|
|
605
|
+
* identities. The result has the same graph kind as `self`. Throws a
|
|
606
|
+
* `GraphError` when the graph kinds do not match. `nodeIdentity` and
|
|
607
|
+
* `edgeIdentity` default to the complete node and edge data.
|
|
608
|
+
*
|
|
609
|
+
* `G1 \ G2 = {V1, E1 \ E2}`
|
|
610
|
+
*
|
|
611
|
+
* **Gotchas**
|
|
612
|
+
*
|
|
613
|
+
* Nodes with equal identities in one input graph are coalesced. The last node
|
|
614
|
+
* supplies the data, and redirected edges can collapse or become self-loops.
|
|
615
|
+
* If `that` contains an edge identity, every parallel edge with that identity
|
|
616
|
+
* is removed from `self`.
|
|
617
|
+
*
|
|
618
|
+
* **Example** (Removing shared edges)
|
|
619
|
+
*
|
|
620
|
+
* ```ts
|
|
621
|
+
* import { Graph } from "effect"
|
|
622
|
+
*
|
|
623
|
+
* const left = Graph.directed<string, string>((mutable) => {
|
|
624
|
+
* const a = Graph.addNode(mutable, "A")
|
|
625
|
+
* const b = Graph.addNode(mutable, "B")
|
|
626
|
+
* const c = Graph.addNode(mutable, "C")
|
|
627
|
+
* Graph.addEdge(mutable, a, b, "A-B")
|
|
628
|
+
* Graph.addEdge(mutable, b, c, "B-C")
|
|
629
|
+
* })
|
|
630
|
+
*
|
|
631
|
+
* const right = Graph.directed<string, string>((mutable) => {
|
|
632
|
+
* const b = Graph.addNode(mutable, "B")
|
|
633
|
+
* const c = Graph.addNode(mutable, "C")
|
|
634
|
+
* Graph.addEdge(mutable, b, c, "B-C")
|
|
635
|
+
* })
|
|
636
|
+
*
|
|
637
|
+
* const result = Graph.difference(left, right)
|
|
638
|
+
*
|
|
639
|
+
* console.log(Graph.nodeCount(result)) // 3
|
|
640
|
+
* console.log(Graph.edgeCount(result)) // 1
|
|
641
|
+
* ```
|
|
642
|
+
*
|
|
643
|
+
* @category set operations
|
|
644
|
+
* @since 4.0.0
|
|
645
|
+
*/
|
|
646
|
+
export const difference = /*#__PURE__*/dual(args => isGraph(args[0]) && isGraph(args[1]), (self, that, options) => {
|
|
647
|
+
assertSameKind(self, that);
|
|
648
|
+
const selfImpl = graphImpl(self);
|
|
649
|
+
const getNodeIdentity = options?.nodeIdentity ?? (node => node);
|
|
650
|
+
const getEdgeIdentity = options?.edgeIdentity ?? (edge => edge);
|
|
651
|
+
const selfMaps = buildNodeMaps(self, getNodeIdentity);
|
|
652
|
+
const thatMaps = buildNodeMaps(that, getNodeIdentity);
|
|
653
|
+
const thatEdges = buildEdgeMap(that, thatMaps, getEdgeIdentity);
|
|
654
|
+
return make(self.type)(mutable => {
|
|
655
|
+
const indexByIdentity = addNodesByIdentity(mutable, selfMaps.byIdentity);
|
|
656
|
+
for (const edge of selfImpl.edges.values()) {
|
|
657
|
+
const sourceIdentity = nodeIdentityAt(selfMaps, edge.source);
|
|
658
|
+
const targetIdentity = nodeIdentityAt(selfMaps, edge.target);
|
|
659
|
+
const edgeIdentity = new EdgeIdentity(self.type, sourceIdentity, targetIdentity, getEdgeIdentity(edge.data));
|
|
660
|
+
if (!MutableHashMap.has(thatEdges, edgeIdentity)) {
|
|
661
|
+
addEdgeByIdentity(mutable, indexByIdentity, edgeIdentity, edge.data);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
});
|
|
665
|
+
});
|
|
666
|
+
/**
|
|
667
|
+
* Returns edges present in exactly one of two graphs.
|
|
668
|
+
*
|
|
669
|
+
* **Details**
|
|
670
|
+
*
|
|
671
|
+
* Keeps nodes from both graphs. Overlapping nodes use data from `that`. The
|
|
672
|
+
* result has the same graph kind as `self`. Throws a `GraphError` when the
|
|
673
|
+
* graph kinds do not match. `nodeIdentity` and `edgeIdentity` default to the
|
|
674
|
+
* complete node and edge data. Edge identity also includes the endpoint
|
|
675
|
+
* identities.
|
|
676
|
+
*
|
|
677
|
+
* `G1 Δ G2 = {V1 ∪ V2, (E1 ∪ E2) \ (E1 ∩ E2)}`
|
|
678
|
+
*
|
|
679
|
+
* **Gotchas**
|
|
680
|
+
*
|
|
681
|
+
* Edges with different projected identities are distinct.
|
|
682
|
+
* Nodes with equal identities in one input graph are coalesced. The last node
|
|
683
|
+
* supplies the data, and redirected edges can collapse or become self-loops.
|
|
684
|
+
* Parallel edges with equal identities are coalesced before the graphs are
|
|
685
|
+
* compared.
|
|
686
|
+
*
|
|
687
|
+
* **Example** (Finding differing edges)
|
|
688
|
+
*
|
|
689
|
+
* ```ts
|
|
690
|
+
* import { Graph } from "effect"
|
|
691
|
+
*
|
|
692
|
+
* const left = Graph.directed<string, string>((mutable) => {
|
|
693
|
+
* const a = Graph.addNode(mutable, "A")
|
|
694
|
+
* const b = Graph.addNode(mutable, "B")
|
|
695
|
+
* const c = Graph.addNode(mutable, "C")
|
|
696
|
+
* Graph.addEdge(mutable, a, b, "A-B")
|
|
697
|
+
* Graph.addEdge(mutable, b, c, "B-C")
|
|
698
|
+
* })
|
|
699
|
+
*
|
|
700
|
+
* const right = Graph.directed<string, string>((mutable) => {
|
|
701
|
+
* const b = Graph.addNode(mutable, "B")
|
|
702
|
+
* const c = Graph.addNode(mutable, "C")
|
|
703
|
+
* const d = Graph.addNode(mutable, "D")
|
|
704
|
+
* Graph.addEdge(mutable, b, c, "B-C")
|
|
705
|
+
* Graph.addEdge(mutable, c, d, "C-D")
|
|
706
|
+
* })
|
|
707
|
+
*
|
|
708
|
+
* const result = Graph.symmetricDifference(left, right)
|
|
709
|
+
*
|
|
710
|
+
* console.log(Graph.nodeCount(result)) // 4
|
|
711
|
+
* console.log(Graph.edgeCount(result)) // 2
|
|
712
|
+
* ```
|
|
713
|
+
*
|
|
714
|
+
* @category set operations
|
|
715
|
+
* @since 4.0.0
|
|
716
|
+
*/
|
|
717
|
+
export const symmetricDifference = /*#__PURE__*/dual(args => isGraph(args[0]) && isGraph(args[1]), (self, that, options) => {
|
|
718
|
+
assertSameKind(self, that);
|
|
719
|
+
const getNodeIdentity = options?.nodeIdentity ?? (node => node);
|
|
720
|
+
const getEdgeIdentity = options?.edgeIdentity ?? (edge => edge);
|
|
721
|
+
const selfMaps = buildNodeMaps(self, getNodeIdentity);
|
|
722
|
+
const thatMaps = buildNodeMaps(that, getNodeIdentity);
|
|
723
|
+
const nodes = MutableHashMap.empty();
|
|
724
|
+
const selfEdges = buildEdgeMap(self, selfMaps, getEdgeIdentity);
|
|
725
|
+
const thatEdges = buildEdgeMap(that, thatMaps, getEdgeIdentity);
|
|
726
|
+
for (const [identity, data] of selfMaps.byIdentity) {
|
|
727
|
+
MutableHashMap.set(nodes, identity, data);
|
|
728
|
+
}
|
|
729
|
+
for (const [identity, data] of thatMaps.byIdentity) {
|
|
730
|
+
MutableHashMap.set(nodes, identity, data);
|
|
731
|
+
}
|
|
732
|
+
return make(self.type)(mutable => {
|
|
733
|
+
const indexByIdentity = addNodesByIdentity(mutable, nodes);
|
|
734
|
+
for (const [identity, data] of selfEdges) {
|
|
735
|
+
if (!MutableHashMap.has(thatEdges, identity)) {
|
|
736
|
+
addEdgeByIdentity(mutable, indexByIdentity, identity, data);
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
for (const [identity, data] of thatEdges) {
|
|
740
|
+
if (!MutableHashMap.has(selfEdges, identity)) {
|
|
741
|
+
addEdgeByIdentity(mutable, indexByIdentity, identity, data);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
});
|
|
745
|
+
});
|
|
746
|
+
/**
|
|
747
|
+
* Returns the complement over the existing node set.
|
|
748
|
+
*
|
|
749
|
+
* **Details**
|
|
750
|
+
*
|
|
751
|
+
* Adds every missing edge between distinct nodes. The `createEdge` function
|
|
752
|
+
* receives the source and target node data for each added edge. The result has
|
|
753
|
+
* the same graph kind as `self`.
|
|
754
|
+
*
|
|
755
|
+
* `G' = {V, (V x V) \ E}`
|
|
756
|
+
*
|
|
757
|
+
* **Example** (Finding missing relationships)
|
|
758
|
+
*
|
|
759
|
+
* ```ts
|
|
760
|
+
* import { Graph } from "effect"
|
|
761
|
+
*
|
|
762
|
+
* const graph = Graph.directed<string, string>((mutable) => {
|
|
763
|
+
* const a = Graph.addNode(mutable, "A")
|
|
764
|
+
* const b = Graph.addNode(mutable, "B")
|
|
765
|
+
* Graph.addEdge(mutable, a, b, "A-B")
|
|
766
|
+
* })
|
|
767
|
+
*
|
|
768
|
+
* const result = Graph.complement(graph, (source, target) => `${source}-${target}`)
|
|
769
|
+
*
|
|
770
|
+
* console.log(Graph.edgeCount(result)) // 1
|
|
771
|
+
* ```
|
|
772
|
+
*
|
|
773
|
+
* @category set operations
|
|
774
|
+
* @since 4.0.0
|
|
775
|
+
*/
|
|
776
|
+
export const complement = /*#__PURE__*/dual(2, (self, createEdge) => {
|
|
777
|
+
const selfImpl = graphImpl(self);
|
|
778
|
+
const nodeEntries = Array.from(selfImpl.nodes);
|
|
779
|
+
return make(self.type)(mutable => {
|
|
780
|
+
const newIndexMap = new Map();
|
|
781
|
+
for (const [oldIndex, data] of nodeEntries) {
|
|
782
|
+
newIndexMap.set(oldIndex, addNode(mutable, data));
|
|
783
|
+
}
|
|
784
|
+
for (let i = 0; i < nodeEntries.length; i++) {
|
|
785
|
+
const [sourceOldIndex, sourceData] = nodeEntries[i];
|
|
786
|
+
const start = self.type === "undirected" ? i + 1 : 0;
|
|
787
|
+
for (let j = start; j < nodeEntries.length; j++) {
|
|
788
|
+
const [targetOldIndex, targetData] = nodeEntries[j];
|
|
789
|
+
if (sourceOldIndex === targetOldIndex || hasEdge(self, sourceOldIndex, targetOldIndex)) {
|
|
790
|
+
continue;
|
|
791
|
+
}
|
|
792
|
+
const sourceIndex = newIndexMap.get(sourceOldIndex);
|
|
793
|
+
const targetIndex = newIndexMap.get(targetOldIndex);
|
|
794
|
+
if (sourceIndex !== undefined && targetIndex !== undefined) {
|
|
795
|
+
addEdge(mutable, sourceIndex, targetIndex, createEdge(sourceData, targetData));
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
});
|
|
800
|
+
});
|
|
801
|
+
/**
|
|
802
|
+
* Returns the induced subgraph containing nodes within a radius of a node.
|
|
803
|
+
*
|
|
804
|
+
* **Details**
|
|
805
|
+
*
|
|
806
|
+
* The `radius` option is the maximum edge distance from `nodeIndex` and
|
|
807
|
+
* defaults to `1`. The `direction` option controls directed graph traversal and
|
|
808
|
+
* defaults to `"outgoing"`. The result has the same graph kind as `self` and
|
|
809
|
+
* keeps all original edges whose endpoints are both reached. `"undirected"`
|
|
810
|
+
* ignores edge direction while finding reachable nodes.
|
|
811
|
+
*
|
|
812
|
+
* **Example** (Getting a local neighborhood)
|
|
813
|
+
*
|
|
814
|
+
* ```ts
|
|
815
|
+
* import { Graph } from "effect"
|
|
816
|
+
*
|
|
817
|
+
* const graph = Graph.directed<string, string>((mutable) => {
|
|
818
|
+
* const a = Graph.addNode(mutable, "A")
|
|
819
|
+
* const b = Graph.addNode(mutable, "B")
|
|
820
|
+
* const c = Graph.addNode(mutable, "C")
|
|
821
|
+
* Graph.addEdge(mutable, a, b, "A-B")
|
|
822
|
+
* Graph.addEdge(mutable, b, c, "B-C")
|
|
823
|
+
* })
|
|
824
|
+
*
|
|
825
|
+
* const result = Graph.neighborhood(graph, 1, { radius: 1 })
|
|
826
|
+
*
|
|
827
|
+
* console.log(Graph.nodeCount(result)) // 2
|
|
828
|
+
* ```
|
|
829
|
+
*
|
|
830
|
+
* @category set operations
|
|
831
|
+
* @since 4.0.0
|
|
832
|
+
*/
|
|
833
|
+
export const neighborhood = /*#__PURE__*/dual(args => isGraph(args[0]), (self, nodeIndex, options) => {
|
|
834
|
+
const selfImpl = graphImpl(self);
|
|
835
|
+
const radius = options?.radius ?? 1;
|
|
836
|
+
const direction = options?.direction ?? "outgoing";
|
|
837
|
+
const reached = new Set();
|
|
838
|
+
for (const index of indices(bfs(self, {
|
|
839
|
+
start: [nodeIndex],
|
|
840
|
+
direction,
|
|
841
|
+
radius
|
|
842
|
+
}))) {
|
|
843
|
+
reached.add(index);
|
|
844
|
+
}
|
|
845
|
+
return make(self.type)(mutable => {
|
|
846
|
+
const newIndexMap = new Map();
|
|
847
|
+
for (const oldIndex of reached) {
|
|
848
|
+
newIndexMap.set(oldIndex, addNode(mutable, Option.getOrThrow(getNode(self, oldIndex))));
|
|
849
|
+
}
|
|
850
|
+
for (const edge of selfImpl.edges.values()) {
|
|
851
|
+
if (reached.has(edge.source) && reached.has(edge.target)) {
|
|
852
|
+
const sourceIndex = newIndexMap.get(edge.source);
|
|
853
|
+
const targetIndex = newIndexMap.get(edge.target);
|
|
854
|
+
if (sourceIndex !== undefined && targetIndex !== undefined) {
|
|
855
|
+
addEdge(mutable, sourceIndex, targetIndex, edge.data);
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
});
|
|
860
|
+
});
|
|
861
|
+
/**
|
|
862
|
+
* Returns the disjoint union of two graphs.
|
|
863
|
+
*
|
|
864
|
+
* **Details**
|
|
865
|
+
*
|
|
866
|
+
* Copies all nodes and edges from both graphs without merging equal node data.
|
|
867
|
+
* The result has the same graph kind as `self`. Throws a `GraphError` when the
|
|
868
|
+
* graph kinds do not match.
|
|
869
|
+
*
|
|
870
|
+
* `G1 + G2 = {disjoint V1 + V2, disjoint E1 + E2}`
|
|
871
|
+
*
|
|
872
|
+
* @category set operations
|
|
873
|
+
* @since 4.0.0
|
|
874
|
+
*/
|
|
875
|
+
export const sum = /*#__PURE__*/dual(2, (self, that) => {
|
|
876
|
+
assertSameKind(self, that);
|
|
877
|
+
return make(self.type)(mutable => {
|
|
878
|
+
const copyInto = graph => {
|
|
879
|
+
const impl = graphImpl(graph);
|
|
880
|
+
const indexMap = new Map();
|
|
881
|
+
for (const [oldIndex, data] of impl.nodes) {
|
|
882
|
+
indexMap.set(oldIndex, addNode(mutable, data));
|
|
883
|
+
}
|
|
884
|
+
for (const edge of impl.edges.values()) {
|
|
885
|
+
const sourceIndex = indexMap.get(edge.source);
|
|
886
|
+
const targetIndex = indexMap.get(edge.target);
|
|
887
|
+
if (sourceIndex !== undefined && targetIndex !== undefined) {
|
|
888
|
+
addEdge(mutable, sourceIndex, targetIndex, edge.data);
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
};
|
|
892
|
+
copyInto(self);
|
|
893
|
+
copyInto(that);
|
|
894
|
+
});
|
|
322
895
|
});
|
|
323
896
|
// =============================================================================
|
|
324
897
|
// Basic Node Operations
|
|
@@ -362,14 +935,16 @@ export const mutate = /*#__PURE__*/dual(2, (graph, f) => {
|
|
|
362
935
|
* @since 3.18.0
|
|
363
936
|
*/
|
|
364
937
|
export const addNode = (mutable, data) => {
|
|
365
|
-
|
|
938
|
+
assertMutable(mutable);
|
|
939
|
+
const impl = graphImpl(mutable);
|
|
940
|
+
const nodeIndex = impl.nextNodeIndex;
|
|
366
941
|
// Add node data
|
|
367
|
-
|
|
942
|
+
impl.nodes.set(nodeIndex, data);
|
|
368
943
|
// Initialize empty adjacency lists
|
|
369
|
-
|
|
370
|
-
|
|
944
|
+
impl.adjacency.set(nodeIndex, []);
|
|
945
|
+
impl.reverseAdjacency.set(nodeIndex, []);
|
|
371
946
|
// Update graph allocators
|
|
372
|
-
|
|
947
|
+
impl.nextNodeIndex = impl.nextNodeIndex + 1;
|
|
373
948
|
return nodeIndex;
|
|
374
949
|
};
|
|
375
950
|
/**
|
|
@@ -395,7 +970,10 @@ export const addNode = (mutable, data) => {
|
|
|
395
970
|
* @category getters
|
|
396
971
|
* @since 3.18.0
|
|
397
972
|
*/
|
|
398
|
-
export const getNode = /*#__PURE__*/dual(2, (graph, nodeIndex) =>
|
|
973
|
+
export const getNode = /*#__PURE__*/dual(2, (graph, nodeIndex) => {
|
|
974
|
+
const impl = graphImpl(graph);
|
|
975
|
+
return impl.nodes.has(nodeIndex) ? Option.some(impl.nodes.get(nodeIndex)) : Option.none();
|
|
976
|
+
});
|
|
399
977
|
/**
|
|
400
978
|
* Checks whether a node with the given index exists in the graph.
|
|
401
979
|
*
|
|
@@ -420,7 +998,7 @@ export const getNode = /*#__PURE__*/dual(2, (graph, nodeIndex) => graph.nodes.ha
|
|
|
420
998
|
* @category getters
|
|
421
999
|
* @since 3.18.0
|
|
422
1000
|
*/
|
|
423
|
-
export const hasNode = /*#__PURE__*/dual(2, (graph, nodeIndex) => graph.nodes.has(nodeIndex));
|
|
1001
|
+
export const hasNode = /*#__PURE__*/dual(2, (graph, nodeIndex) => graphImpl(graph).nodes.has(nodeIndex));
|
|
424
1002
|
/**
|
|
425
1003
|
* Returns the number of nodes in the graph.
|
|
426
1004
|
*
|
|
@@ -444,7 +1022,7 @@ export const hasNode = /*#__PURE__*/dual(2, (graph, nodeIndex) => graph.nodes.ha
|
|
|
444
1022
|
* @category getters
|
|
445
1023
|
* @since 3.18.0
|
|
446
1024
|
*/
|
|
447
|
-
export const nodeCount = graph => graph.nodes.size;
|
|
1025
|
+
export const nodeCount = graph => graphImpl(graph).nodes.size;
|
|
448
1026
|
/**
|
|
449
1027
|
* Finds the first node that matches the given predicate.
|
|
450
1028
|
*
|
|
@@ -470,7 +1048,8 @@ export const nodeCount = graph => graph.nodes.size;
|
|
|
470
1048
|
* @since 3.18.0
|
|
471
1049
|
*/
|
|
472
1050
|
export const findNode = /*#__PURE__*/dual(2, (graph, predicate) => {
|
|
473
|
-
|
|
1051
|
+
const impl = graphImpl(graph);
|
|
1052
|
+
for (const [index, data] of impl.nodes) {
|
|
474
1053
|
if (predicate(data)) {
|
|
475
1054
|
return Option.some(index);
|
|
476
1055
|
}
|
|
@@ -502,8 +1081,9 @@ export const findNode = /*#__PURE__*/dual(2, (graph, predicate) => {
|
|
|
502
1081
|
* @since 3.18.0
|
|
503
1082
|
*/
|
|
504
1083
|
export const findNodes = /*#__PURE__*/dual(2, (graph, predicate) => {
|
|
1084
|
+
const impl = graphImpl(graph);
|
|
505
1085
|
const results = [];
|
|
506
|
-
for (const [index, data] of
|
|
1086
|
+
for (const [index, data] of impl.nodes) {
|
|
507
1087
|
if (predicate(data)) {
|
|
508
1088
|
results.push(index);
|
|
509
1089
|
}
|
|
@@ -537,7 +1117,8 @@ export const findNodes = /*#__PURE__*/dual(2, (graph, predicate) => {
|
|
|
537
1117
|
* @since 3.18.0
|
|
538
1118
|
*/
|
|
539
1119
|
export const findEdge = /*#__PURE__*/dual(2, (graph, predicate) => {
|
|
540
|
-
|
|
1120
|
+
const impl = graphImpl(graph);
|
|
1121
|
+
for (const [edgeIndex, edgeData] of impl.edges) {
|
|
541
1122
|
if (predicate(edgeData.data, edgeData.source, edgeData.target)) {
|
|
542
1123
|
return Option.some(edgeIndex);
|
|
543
1124
|
}
|
|
@@ -572,8 +1153,9 @@ export const findEdge = /*#__PURE__*/dual(2, (graph, predicate) => {
|
|
|
572
1153
|
* @since 3.18.0
|
|
573
1154
|
*/
|
|
574
1155
|
export const findEdges = /*#__PURE__*/dual(2, (graph, predicate) => {
|
|
1156
|
+
const impl = graphImpl(graph);
|
|
575
1157
|
const results = [];
|
|
576
|
-
for (const [edgeIndex, edgeData] of
|
|
1158
|
+
for (const [edgeIndex, edgeData] of impl.edges) {
|
|
577
1159
|
if (predicate(edgeData.data, edgeData.source, edgeData.target)) {
|
|
578
1160
|
results.push(edgeIndex);
|
|
579
1161
|
}
|
|
@@ -602,12 +1184,14 @@ export const findEdges = /*#__PURE__*/dual(2, (graph, predicate) => {
|
|
|
602
1184
|
* @since 3.18.0
|
|
603
1185
|
*/
|
|
604
1186
|
export const updateNode = (mutable, index, f) => {
|
|
605
|
-
|
|
1187
|
+
assertMutable(mutable);
|
|
1188
|
+
const impl = graphImpl(mutable);
|
|
1189
|
+
if (!impl.nodes.has(index)) {
|
|
606
1190
|
return;
|
|
607
1191
|
}
|
|
608
|
-
const currentData =
|
|
1192
|
+
const currentData = impl.nodes.get(index);
|
|
609
1193
|
const newData = f(currentData);
|
|
610
|
-
|
|
1194
|
+
impl.nodes.set(index, newData);
|
|
611
1195
|
};
|
|
612
1196
|
/**
|
|
613
1197
|
* Updates a single edge's data by applying a transformation function.
|
|
@@ -632,12 +1216,14 @@ export const updateNode = (mutable, index, f) => {
|
|
|
632
1216
|
* @since 3.18.0
|
|
633
1217
|
*/
|
|
634
1218
|
export const updateEdge = (mutable, edgeIndex, f) => {
|
|
635
|
-
|
|
1219
|
+
assertMutable(mutable);
|
|
1220
|
+
const impl = graphImpl(mutable);
|
|
1221
|
+
if (!impl.edges.has(edgeIndex)) {
|
|
636
1222
|
return;
|
|
637
1223
|
}
|
|
638
|
-
const currentEdge =
|
|
1224
|
+
const currentEdge = impl.edges.get(edgeIndex);
|
|
639
1225
|
const newData = f(currentEdge.data);
|
|
640
|
-
|
|
1226
|
+
impl.edges.set(edgeIndex, new Edge({
|
|
641
1227
|
...currentEdge,
|
|
642
1228
|
data: newData
|
|
643
1229
|
}));
|
|
@@ -670,10 +1256,12 @@ export const updateEdge = (mutable, edgeIndex, f) => {
|
|
|
670
1256
|
* @since 3.18.0
|
|
671
1257
|
*/
|
|
672
1258
|
export const mapNodes = (mutable, f) => {
|
|
1259
|
+
assertMutable(mutable);
|
|
1260
|
+
const impl = graphImpl(mutable);
|
|
673
1261
|
// Transform existing node data in place
|
|
674
|
-
for (const [index, data] of
|
|
1262
|
+
for (const [index, data] of impl.nodes) {
|
|
675
1263
|
const newData = f(data);
|
|
676
|
-
|
|
1264
|
+
impl.nodes.set(index, newData);
|
|
677
1265
|
}
|
|
678
1266
|
};
|
|
679
1267
|
/**
|
|
@@ -701,13 +1289,15 @@ export const mapNodes = (mutable, f) => {
|
|
|
701
1289
|
* @since 3.18.0
|
|
702
1290
|
*/
|
|
703
1291
|
export const mapEdges = (mutable, f) => {
|
|
1292
|
+
assertMutable(mutable);
|
|
1293
|
+
const impl = graphImpl(mutable);
|
|
704
1294
|
// Transform existing edge data in place
|
|
705
|
-
for (const [index, edgeData] of
|
|
1295
|
+
for (const [index, edgeData] of impl.edges) {
|
|
706
1296
|
const newData = f(edgeData.data);
|
|
707
|
-
|
|
1297
|
+
impl.edges.set(index, new Edge({
|
|
708
1298
|
...edgeData,
|
|
709
1299
|
data: newData
|
|
710
|
-
});
|
|
1300
|
+
}));
|
|
711
1301
|
}
|
|
712
1302
|
};
|
|
713
1303
|
/**
|
|
@@ -754,20 +1344,22 @@ const rebuildAdjacency = mutable => {
|
|
|
754
1344
|
* @since 3.18.0
|
|
755
1345
|
*/
|
|
756
1346
|
export const reverse = mutable => {
|
|
757
|
-
|
|
1347
|
+
assertMutable(mutable);
|
|
1348
|
+
const impl = graphImpl(mutable);
|
|
1349
|
+
if (impl.type === "undirected") {
|
|
758
1350
|
return;
|
|
759
1351
|
}
|
|
760
1352
|
// Reverse all edges by swapping source and target
|
|
761
|
-
for (const [index, edgeData] of
|
|
762
|
-
|
|
1353
|
+
for (const [index, edgeData] of impl.edges) {
|
|
1354
|
+
impl.edges.set(index, new Edge({
|
|
763
1355
|
source: edgeData.target,
|
|
764
1356
|
target: edgeData.source,
|
|
765
1357
|
data: edgeData.data
|
|
766
1358
|
}));
|
|
767
1359
|
}
|
|
768
|
-
rebuildAdjacency(
|
|
1360
|
+
rebuildAdjacency(impl);
|
|
769
1361
|
// Invalidate cycle flag since edge directions changed
|
|
770
|
-
|
|
1362
|
+
impl.acyclic = Option.none();
|
|
771
1363
|
};
|
|
772
1364
|
/**
|
|
773
1365
|
* Filters and optionally transforms nodes in a mutable graph using a predicate function.
|
|
@@ -800,13 +1392,15 @@ export const reverse = mutable => {
|
|
|
800
1392
|
* @since 3.18.0
|
|
801
1393
|
*/
|
|
802
1394
|
export const filterMapNodes = (mutable, f) => {
|
|
1395
|
+
assertMutable(mutable);
|
|
1396
|
+
const impl = graphImpl(mutable);
|
|
803
1397
|
const nodesToRemove = [];
|
|
804
1398
|
// First pass: identify nodes to remove and transform data for nodes to keep
|
|
805
|
-
for (const [index, data] of
|
|
1399
|
+
for (const [index, data] of impl.nodes) {
|
|
806
1400
|
const result = f(data);
|
|
807
1401
|
if (Option.isSome(result)) {
|
|
808
1402
|
// Transform node data
|
|
809
|
-
|
|
1403
|
+
impl.nodes.set(index, result.value);
|
|
810
1404
|
} else {
|
|
811
1405
|
// Mark for removal
|
|
812
1406
|
nodesToRemove.push(index);
|
|
@@ -848,16 +1442,18 @@ export const filterMapNodes = (mutable, f) => {
|
|
|
848
1442
|
* @since 3.18.0
|
|
849
1443
|
*/
|
|
850
1444
|
export const filterMapEdges = (mutable, f) => {
|
|
1445
|
+
assertMutable(mutable);
|
|
1446
|
+
const impl = graphImpl(mutable);
|
|
851
1447
|
const edgesToRemove = [];
|
|
852
1448
|
// First pass: identify edges to remove and transform data for edges to keep
|
|
853
|
-
for (const [index, edgeData] of
|
|
1449
|
+
for (const [index, edgeData] of impl.edges) {
|
|
854
1450
|
const result = f(edgeData.data);
|
|
855
1451
|
if (Option.isSome(result)) {
|
|
856
1452
|
// Transform edge data
|
|
857
|
-
|
|
1453
|
+
impl.edges.set(index, new Edge({
|
|
858
1454
|
...edgeData,
|
|
859
1455
|
data: result.value
|
|
860
|
-
});
|
|
1456
|
+
}));
|
|
861
1457
|
} else {
|
|
862
1458
|
// Mark for removal
|
|
863
1459
|
edgesToRemove.push(index);
|
|
@@ -894,9 +1490,11 @@ export const filterMapEdges = (mutable, f) => {
|
|
|
894
1490
|
* @since 3.18.0
|
|
895
1491
|
*/
|
|
896
1492
|
export const filterNodes = (mutable, predicate) => {
|
|
1493
|
+
assertMutable(mutable);
|
|
1494
|
+
const impl = graphImpl(mutable);
|
|
897
1495
|
const nodesToRemove = [];
|
|
898
1496
|
// Identify nodes to remove
|
|
899
|
-
for (const [index, data] of
|
|
1497
|
+
for (const [index, data] of impl.nodes) {
|
|
900
1498
|
if (!predicate(data)) {
|
|
901
1499
|
nodesToRemove.push(index);
|
|
902
1500
|
}
|
|
@@ -935,9 +1533,11 @@ export const filterNodes = (mutable, predicate) => {
|
|
|
935
1533
|
* @since 3.18.0
|
|
936
1534
|
*/
|
|
937
1535
|
export const filterEdges = (mutable, predicate) => {
|
|
1536
|
+
assertMutable(mutable);
|
|
1537
|
+
const impl = graphImpl(mutable);
|
|
938
1538
|
const edgesToRemove = [];
|
|
939
1539
|
// Identify edges to remove
|
|
940
|
-
for (const [index, edgeData] of
|
|
1540
|
+
for (const [index, edgeData] of impl.edges) {
|
|
941
1541
|
if (!predicate(edgeData.data)) {
|
|
942
1542
|
edgesToRemove.push(index);
|
|
943
1543
|
}
|
|
@@ -1008,46 +1608,48 @@ const invalidateCycleFlagOnAddition = mutable => {
|
|
|
1008
1608
|
* @since 3.18.0
|
|
1009
1609
|
*/
|
|
1010
1610
|
export const addEdge = (mutable, source, target, data) => {
|
|
1611
|
+
assertMutable(mutable);
|
|
1612
|
+
const impl = graphImpl(mutable);
|
|
1011
1613
|
// Validate that both nodes exist
|
|
1012
|
-
if (!
|
|
1614
|
+
if (!impl.nodes.has(source)) {
|
|
1013
1615
|
throw missingNode(source);
|
|
1014
1616
|
}
|
|
1015
|
-
if (!
|
|
1617
|
+
if (!impl.nodes.has(target)) {
|
|
1016
1618
|
throw missingNode(target);
|
|
1017
1619
|
}
|
|
1018
|
-
const edgeIndex =
|
|
1620
|
+
const edgeIndex = impl.nextEdgeIndex;
|
|
1019
1621
|
// Create edge data
|
|
1020
1622
|
const edgeData = new Edge({
|
|
1021
1623
|
source,
|
|
1022
1624
|
target,
|
|
1023
1625
|
data
|
|
1024
1626
|
});
|
|
1025
|
-
|
|
1627
|
+
impl.edges.set(edgeIndex, edgeData);
|
|
1026
1628
|
// Update adjacency lists
|
|
1027
|
-
const sourceAdjacency =
|
|
1629
|
+
const sourceAdjacency = impl.adjacency.get(source);
|
|
1028
1630
|
if (sourceAdjacency !== undefined) {
|
|
1029
1631
|
sourceAdjacency.push(edgeIndex);
|
|
1030
1632
|
}
|
|
1031
|
-
const targetReverseAdjacency =
|
|
1633
|
+
const targetReverseAdjacency = impl.reverseAdjacency.get(target);
|
|
1032
1634
|
if (targetReverseAdjacency !== undefined) {
|
|
1033
1635
|
targetReverseAdjacency.push(edgeIndex);
|
|
1034
1636
|
}
|
|
1035
1637
|
// For undirected graphs, add reverse connections
|
|
1036
|
-
if (
|
|
1037
|
-
const targetAdjacency =
|
|
1638
|
+
if (impl.type === "undirected") {
|
|
1639
|
+
const targetAdjacency = impl.adjacency.get(target);
|
|
1038
1640
|
if (targetAdjacency !== undefined) {
|
|
1039
1641
|
targetAdjacency.push(edgeIndex);
|
|
1040
1642
|
}
|
|
1041
|
-
const sourceReverseAdjacency =
|
|
1643
|
+
const sourceReverseAdjacency = impl.reverseAdjacency.get(source);
|
|
1042
1644
|
if (sourceReverseAdjacency !== undefined) {
|
|
1043
1645
|
sourceReverseAdjacency.push(edgeIndex);
|
|
1044
1646
|
}
|
|
1045
1647
|
}
|
|
1046
1648
|
// Update allocators
|
|
1047
|
-
|
|
1649
|
+
impl.nextEdgeIndex = impl.nextEdgeIndex + 1;
|
|
1048
1650
|
// Only invalidate cycle flag if the graph was acyclic
|
|
1049
1651
|
// Adding edges cannot remove cycles from cyclic graphs
|
|
1050
|
-
invalidateCycleFlagOnAddition(
|
|
1652
|
+
invalidateCycleFlagOnAddition(impl);
|
|
1051
1653
|
return edgeIndex;
|
|
1052
1654
|
};
|
|
1053
1655
|
/**
|
|
@@ -1072,21 +1674,23 @@ export const addEdge = (mutable, source, target, data) => {
|
|
|
1072
1674
|
* @since 3.18.0
|
|
1073
1675
|
*/
|
|
1074
1676
|
export const removeNode = (mutable, nodeIndex) => {
|
|
1677
|
+
assertMutable(mutable);
|
|
1678
|
+
const impl = graphImpl(mutable);
|
|
1075
1679
|
// Check if node exists
|
|
1076
|
-
if (!
|
|
1680
|
+
if (!impl.nodes.has(nodeIndex)) {
|
|
1077
1681
|
return; // Node doesn't exist, nothing to remove
|
|
1078
1682
|
}
|
|
1079
1683
|
// Collect all incident edges for removal
|
|
1080
1684
|
const edgesToRemove = [];
|
|
1081
1685
|
// Get outgoing edges
|
|
1082
|
-
const outgoingEdges =
|
|
1686
|
+
const outgoingEdges = impl.adjacency.get(nodeIndex);
|
|
1083
1687
|
if (outgoingEdges !== undefined) {
|
|
1084
1688
|
for (const edge of outgoingEdges) {
|
|
1085
1689
|
edgesToRemove.push(edge);
|
|
1086
1690
|
}
|
|
1087
1691
|
}
|
|
1088
1692
|
// Get incoming edges
|
|
1089
|
-
const incomingEdges =
|
|
1693
|
+
const incomingEdges = impl.reverseAdjacency.get(nodeIndex);
|
|
1090
1694
|
if (incomingEdges !== undefined) {
|
|
1091
1695
|
for (const edge of incomingEdges) {
|
|
1092
1696
|
edgesToRemove.push(edge);
|
|
@@ -1094,15 +1698,15 @@ export const removeNode = (mutable, nodeIndex) => {
|
|
|
1094
1698
|
}
|
|
1095
1699
|
// Remove all incident edges
|
|
1096
1700
|
for (const edgeIndex of edgesToRemove) {
|
|
1097
|
-
removeEdgeInternal(
|
|
1701
|
+
removeEdgeInternal(impl, edgeIndex);
|
|
1098
1702
|
}
|
|
1099
1703
|
// Remove the node itself
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1704
|
+
impl.nodes.delete(nodeIndex);
|
|
1705
|
+
impl.adjacency.delete(nodeIndex);
|
|
1706
|
+
impl.reverseAdjacency.delete(nodeIndex);
|
|
1103
1707
|
// Only invalidate cycle flag if the graph wasn't already known to be acyclic
|
|
1104
1708
|
// Removing nodes cannot introduce cycles in an acyclic graph
|
|
1105
|
-
invalidateCycleFlagOnRemoval(
|
|
1709
|
+
invalidateCycleFlagOnRemoval(impl);
|
|
1106
1710
|
};
|
|
1107
1711
|
/**
|
|
1108
1712
|
* Removes an edge from a mutable graph.
|
|
@@ -1126,11 +1730,13 @@ export const removeNode = (mutable, nodeIndex) => {
|
|
|
1126
1730
|
* @since 3.18.0
|
|
1127
1731
|
*/
|
|
1128
1732
|
export const removeEdge = (mutable, edgeIndex) => {
|
|
1129
|
-
|
|
1733
|
+
assertMutable(mutable);
|
|
1734
|
+
const impl = graphImpl(mutable);
|
|
1735
|
+
const wasRemoved = removeEdgeInternal(impl, edgeIndex);
|
|
1130
1736
|
// Only invalidate cycle flag if an edge was actually removed
|
|
1131
1737
|
// and only if the graph wasn't already known to be acyclic
|
|
1132
1738
|
if (wasRemoved) {
|
|
1133
|
-
invalidateCycleFlagOnRemoval(
|
|
1739
|
+
invalidateCycleFlagOnRemoval(impl);
|
|
1134
1740
|
}
|
|
1135
1741
|
};
|
|
1136
1742
|
/** @internal */
|
|
@@ -1210,7 +1816,7 @@ const removeEdgeInternal = (mutable, edgeIndex) => {
|
|
|
1210
1816
|
* @category getters
|
|
1211
1817
|
* @since 3.18.0
|
|
1212
1818
|
*/
|
|
1213
|
-
export const getEdge = /*#__PURE__*/dual(2, (graph, edgeIndex) => Option.fromUndefinedOr(graph.edges.get(edgeIndex)));
|
|
1819
|
+
export const getEdge = /*#__PURE__*/dual(2, (graph, edgeIndex) => Option.fromUndefinedOr(graphImpl(graph).edges.get(edgeIndex)));
|
|
1214
1820
|
/**
|
|
1215
1821
|
* Checks whether an edge exists between two nodes in the graph.
|
|
1216
1822
|
*
|
|
@@ -1241,13 +1847,14 @@ export const getEdge = /*#__PURE__*/dual(2, (graph, edgeIndex) => Option.fromUnd
|
|
|
1241
1847
|
* @since 3.18.0
|
|
1242
1848
|
*/
|
|
1243
1849
|
export const hasEdge = /*#__PURE__*/dual(3, (graph, source, target) => {
|
|
1244
|
-
const
|
|
1850
|
+
const impl = graphImpl(graph);
|
|
1851
|
+
const adjacencyList = impl.adjacency.get(source);
|
|
1245
1852
|
if (adjacencyList === undefined) {
|
|
1246
1853
|
return false;
|
|
1247
1854
|
}
|
|
1248
1855
|
// Check if any edge in the adjacency list connects to the target
|
|
1249
1856
|
for (const edgeIndex of adjacencyList) {
|
|
1250
|
-
const edge =
|
|
1857
|
+
const edge = impl.edges.get(edgeIndex);
|
|
1251
1858
|
if (edge !== undefined) {
|
|
1252
1859
|
const neighbor = graph.type === "undirected" && edge.target === source ? edge.source : edge.target;
|
|
1253
1860
|
if (neighbor === target) {
|
|
@@ -1283,16 +1890,17 @@ export const hasEdge = /*#__PURE__*/dual(3, (graph, source, target) => {
|
|
|
1283
1890
|
* @category getters
|
|
1284
1891
|
* @since 3.18.0
|
|
1285
1892
|
*/
|
|
1286
|
-
export const edgeCount = graph => graph.edges.size;
|
|
1893
|
+
export const edgeCount = graph => graphImpl(graph).edges.size;
|
|
1287
1894
|
const getDirectedNeighbors = (graph, nodeIndex, direction) => {
|
|
1288
|
-
const
|
|
1895
|
+
const impl = graphImpl(graph);
|
|
1896
|
+
const adjacencyMap = direction === "incoming" ? impl.reverseAdjacency : impl.adjacency;
|
|
1289
1897
|
const adjacencyList = adjacencyMap.get(nodeIndex);
|
|
1290
1898
|
if (adjacencyList === undefined) {
|
|
1291
1899
|
return [];
|
|
1292
1900
|
}
|
|
1293
1901
|
const result = [];
|
|
1294
1902
|
for (const edgeIndex of adjacencyList) {
|
|
1295
|
-
const edge =
|
|
1903
|
+
const edge = impl.edges.get(edgeIndex);
|
|
1296
1904
|
if (edge !== undefined) {
|
|
1297
1905
|
result.push(direction === "incoming" ? edge.source : edge.target);
|
|
1298
1906
|
}
|
|
@@ -1440,6 +2048,7 @@ export const neighborsDirected = /*#__PURE__*/dual(3, (graph, nodeIndex, directi
|
|
|
1440
2048
|
}
|
|
1441
2049
|
return getDirectedNeighbors(graph, nodeIndex, direction);
|
|
1442
2050
|
});
|
|
2051
|
+
const escapeGraphVizString = value => value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\r\n|\r|\n/g, "\\n");
|
|
1443
2052
|
/**
|
|
1444
2053
|
* Exports a graph to GraphViz DOT format for visualization.
|
|
1445
2054
|
*
|
|
@@ -1459,7 +2068,7 @@ export const neighborsDirected = /*#__PURE__*/dual(3, (graph, nodeIndex, directi
|
|
|
1459
2068
|
*
|
|
1460
2069
|
* const dot = Graph.toGraphViz(graph)
|
|
1461
2070
|
* console.log(dot)
|
|
1462
|
-
* // digraph G {
|
|
2071
|
+
* // digraph "G" {
|
|
1463
2072
|
* // "0" [label="Node A"];
|
|
1464
2073
|
* // "1" [label="Node B"];
|
|
1465
2074
|
* // "2" [label="Node C"];
|
|
@@ -1473,6 +2082,7 @@ export const neighborsDirected = /*#__PURE__*/dual(3, (graph, nodeIndex, directi
|
|
|
1473
2082
|
* @since 3.18.0
|
|
1474
2083
|
*/
|
|
1475
2084
|
export const toGraphViz = /*#__PURE__*/dual(args => isGraph(args[0]), (graph, options) => {
|
|
2085
|
+
const impl = graphImpl(graph);
|
|
1476
2086
|
const {
|
|
1477
2087
|
edgeLabel = data => String(data),
|
|
1478
2088
|
graphName = "G",
|
|
@@ -1481,16 +2091,17 @@ export const toGraphViz = /*#__PURE__*/dual(args => isGraph(args[0]), (graph, op
|
|
|
1481
2091
|
const isDirected = graph.type === "directed";
|
|
1482
2092
|
const graphType = isDirected ? "digraph" : "graph";
|
|
1483
2093
|
const edgeOperator = isDirected ? "->" : "--";
|
|
2094
|
+
const graphId = `"${escapeGraphVizString(graphName)}"`;
|
|
1484
2095
|
const lines = [];
|
|
1485
|
-
lines.push(`${graphType} ${
|
|
2096
|
+
lines.push(`${graphType} ${graphId} {`);
|
|
1486
2097
|
// Add nodes
|
|
1487
|
-
for (const [nodeIndex, nodeData] of
|
|
1488
|
-
const label = nodeLabel(nodeData)
|
|
2098
|
+
for (const [nodeIndex, nodeData] of impl.nodes) {
|
|
2099
|
+
const label = escapeGraphVizString(nodeLabel(nodeData));
|
|
1489
2100
|
lines.push(` "${nodeIndex}" [label="${label}"];`);
|
|
1490
2101
|
}
|
|
1491
2102
|
// Add edges
|
|
1492
|
-
for (const [, edgeData] of
|
|
1493
|
-
const label = edgeLabel(edgeData.data)
|
|
2103
|
+
for (const [, edgeData] of impl.edges) {
|
|
2104
|
+
const label = escapeGraphVizString(edgeLabel(edgeData.data));
|
|
1494
2105
|
lines.push(` "${edgeData.source}" ${edgeOperator} "${edgeData.target}" [label="${label}"];`);
|
|
1495
2106
|
}
|
|
1496
2107
|
lines.push("}");
|
|
@@ -1661,22 +2272,22 @@ const formatMermaidNode = (nodeId, label, shape) => {
|
|
|
1661
2272
|
* name: "MyApp",
|
|
1662
2273
|
* version: "1.0.0",
|
|
1663
2274
|
* type: "library"
|
|
1664
|
-
* })
|
|
2275
|
+
* } satisfies Dependency)
|
|
1665
2276
|
* const react = Graph.addNode(mutable, {
|
|
1666
2277
|
* name: "React",
|
|
1667
2278
|
* version: "18.0.0",
|
|
1668
2279
|
* type: "framework"
|
|
1669
|
-
* })
|
|
2280
|
+
* } satisfies Dependency)
|
|
1670
2281
|
* const lodash = Graph.addNode(mutable, {
|
|
1671
2282
|
* name: "Lodash",
|
|
1672
2283
|
* version: "4.17.0",
|
|
1673
2284
|
* type: "library"
|
|
1674
|
-
* })
|
|
2285
|
+
* } satisfies Dependency)
|
|
1675
2286
|
* const webpack = Graph.addNode(mutable, {
|
|
1676
2287
|
* name: "Webpack",
|
|
1677
2288
|
* version: "5.0.0",
|
|
1678
2289
|
* type: "tool"
|
|
1679
|
-
* })
|
|
2290
|
+
* } satisfies Dependency)
|
|
1680
2291
|
*
|
|
1681
2292
|
* Graph.addEdge(mutable, app, react, "depends on")
|
|
1682
2293
|
* Graph.addEdge(mutable, app, lodash, "depends on")
|
|
@@ -1710,6 +2321,7 @@ const formatMermaidNode = (nodeId, label, shape) => {
|
|
|
1710
2321
|
* @since 3.18.0
|
|
1711
2322
|
*/
|
|
1712
2323
|
export const toMermaid = /*#__PURE__*/dual(args => isGraph(args[0]), (graph, options) => {
|
|
2324
|
+
const impl = graphImpl(graph);
|
|
1713
2325
|
// Extract and validate options with defaults
|
|
1714
2326
|
const {
|
|
1715
2327
|
diagramType,
|
|
@@ -1724,7 +2336,7 @@ export const toMermaid = /*#__PURE__*/dual(args => isGraph(args[0]), (graph, opt
|
|
|
1724
2336
|
const lines = [];
|
|
1725
2337
|
lines.push(`${finalDiagramType} ${direction}`);
|
|
1726
2338
|
// Add nodes
|
|
1727
|
-
for (const [nodeIndex, nodeData] of
|
|
2339
|
+
for (const [nodeIndex, nodeData] of impl.nodes) {
|
|
1728
2340
|
const nodeId = String(nodeIndex);
|
|
1729
2341
|
const label = escapeMermaidLabel(nodeLabel(nodeData));
|
|
1730
2342
|
const shape = nodeShape(nodeData);
|
|
@@ -1733,7 +2345,7 @@ export const toMermaid = /*#__PURE__*/dual(args => isGraph(args[0]), (graph, opt
|
|
|
1733
2345
|
}
|
|
1734
2346
|
// Add edges
|
|
1735
2347
|
const edgeOperator = finalDiagramType === "flowchart" ? "-->" : "---";
|
|
1736
|
-
for (const [, edgeData] of
|
|
2348
|
+
for (const [, edgeData] of impl.edges) {
|
|
1737
2349
|
const sourceId = String(edgeData.source);
|
|
1738
2350
|
const targetId = String(edgeData.target);
|
|
1739
2351
|
const label = escapeMermaidLabel(edgeLabel(edgeData.data));
|
|
@@ -1755,7 +2367,8 @@ export const toMermaid = /*#__PURE__*/dual(args => isGraph(args[0]), (graph, opt
|
|
|
1755
2367
|
*
|
|
1756
2368
|
* Uses depth-first search to detect back edges, which indicate cycles.
|
|
1757
2369
|
* For directed graphs, any back edge creates a cycle. For undirected graphs,
|
|
1758
|
-
* a back edge that doesn't
|
|
2370
|
+
* a back edge that doesn't use the same edge used to enter the current node
|
|
2371
|
+
* creates a cycle.
|
|
1759
2372
|
*
|
|
1760
2373
|
* **Example** (Checking cycles)
|
|
1761
2374
|
*
|
|
@@ -1786,49 +2399,61 @@ export const toMermaid = /*#__PURE__*/dual(args => isGraph(args[0]), (graph, opt
|
|
|
1786
2399
|
* @since 3.18.0
|
|
1787
2400
|
*/
|
|
1788
2401
|
export const isAcyclic = graph => {
|
|
2402
|
+
const impl = graphImpl(graph);
|
|
1789
2403
|
// Use existing cycle flag if available
|
|
1790
|
-
if (Option.isSome(
|
|
1791
|
-
return
|
|
2404
|
+
if (Option.isSome(impl.acyclic)) {
|
|
2405
|
+
return impl.acyclic.value;
|
|
1792
2406
|
}
|
|
1793
2407
|
if (graph.type === "undirected") {
|
|
1794
2408
|
const visited = new Set();
|
|
1795
|
-
for (const startNode of
|
|
2409
|
+
for (const startNode of impl.nodes.keys()) {
|
|
1796
2410
|
if (visited.has(startNode)) {
|
|
1797
2411
|
continue;
|
|
1798
2412
|
}
|
|
1799
2413
|
visited.add(startNode);
|
|
1800
2414
|
const stack = [{
|
|
1801
2415
|
node: startNode,
|
|
1802
|
-
|
|
2416
|
+
incoming: null
|
|
1803
2417
|
}];
|
|
1804
2418
|
while (stack.length > 0) {
|
|
1805
2419
|
const {
|
|
1806
2420
|
node,
|
|
1807
|
-
|
|
2421
|
+
incoming
|
|
1808
2422
|
} = stack.pop();
|
|
1809
|
-
const
|
|
1810
|
-
|
|
2423
|
+
const adjacencyList = impl.adjacency.get(node);
|
|
2424
|
+
if (adjacencyList === undefined) {
|
|
2425
|
+
continue;
|
|
2426
|
+
}
|
|
2427
|
+
for (const edgeIndex of adjacencyList) {
|
|
2428
|
+
if (edgeIndex === incoming) {
|
|
2429
|
+
continue;
|
|
2430
|
+
}
|
|
2431
|
+
const edge = impl.edges.get(edgeIndex);
|
|
2432
|
+
if (edge === undefined) {
|
|
2433
|
+
continue;
|
|
2434
|
+
}
|
|
2435
|
+
const neighbor = getTraversableNeighbor(graph, node, edge);
|
|
1811
2436
|
if (!visited.has(neighbor)) {
|
|
1812
2437
|
visited.add(neighbor);
|
|
1813
2438
|
stack.push({
|
|
1814
2439
|
node: neighbor,
|
|
1815
|
-
|
|
2440
|
+
incoming: edgeIndex
|
|
1816
2441
|
});
|
|
1817
|
-
} else
|
|
1818
|
-
|
|
2442
|
+
} else {
|
|
2443
|
+
impl.acyclic = Option.some(false);
|
|
1819
2444
|
return false;
|
|
1820
2445
|
}
|
|
1821
2446
|
}
|
|
1822
2447
|
}
|
|
1823
2448
|
}
|
|
1824
|
-
|
|
2449
|
+
impl.acyclic = Option.some(true);
|
|
1825
2450
|
return true;
|
|
1826
2451
|
}
|
|
1827
2452
|
// Stack-safe DFS cycle detection using iterative approach
|
|
1828
2453
|
const visited = new Set();
|
|
1829
2454
|
const recursionStack = new Set();
|
|
1830
2455
|
// Get all nodes to handle disconnected components
|
|
1831
|
-
for (const startNode of
|
|
2456
|
+
for (const startNode of impl.nodes.keys()) {
|
|
1832
2457
|
if (visited.has(startNode)) {
|
|
1833
2458
|
continue; // Already processed this component
|
|
1834
2459
|
}
|
|
@@ -1840,7 +2465,7 @@ export const isAcyclic = graph => {
|
|
|
1840
2465
|
if (isFirstVisit) {
|
|
1841
2466
|
if (recursionStack.has(node)) {
|
|
1842
2467
|
// Back edge found - cycle detected
|
|
1843
|
-
|
|
2468
|
+
impl.acyclic = Option.some(false);
|
|
1844
2469
|
return false;
|
|
1845
2470
|
}
|
|
1846
2471
|
if (visited.has(node)) {
|
|
@@ -1860,7 +2485,7 @@ export const isAcyclic = graph => {
|
|
|
1860
2485
|
stack[stack.length - 1] = [node, neighbors, neighborIndex + 1, false];
|
|
1861
2486
|
if (recursionStack.has(neighbor)) {
|
|
1862
2487
|
// Back edge found - cycle detected
|
|
1863
|
-
|
|
2488
|
+
impl.acyclic = Option.some(false);
|
|
1864
2489
|
return false;
|
|
1865
2490
|
}
|
|
1866
2491
|
if (!visited.has(neighbor)) {
|
|
@@ -1874,7 +2499,7 @@ export const isAcyclic = graph => {
|
|
|
1874
2499
|
}
|
|
1875
2500
|
}
|
|
1876
2501
|
// Cache the result
|
|
1877
|
-
|
|
2502
|
+
impl.acyclic = Option.some(true);
|
|
1878
2503
|
return true;
|
|
1879
2504
|
};
|
|
1880
2505
|
/**
|
|
@@ -1919,11 +2544,12 @@ export const isAcyclic = graph => {
|
|
|
1919
2544
|
* @since 3.18.0
|
|
1920
2545
|
*/
|
|
1921
2546
|
export const isBipartite = graph => {
|
|
2547
|
+
const impl = graphImpl(graph);
|
|
1922
2548
|
const coloring = new Map();
|
|
1923
2549
|
const discovered = new Set();
|
|
1924
2550
|
let isBipartiteGraph = true;
|
|
1925
2551
|
// Get all nodes to handle disconnected components
|
|
1926
|
-
for (const startNode of
|
|
2552
|
+
for (const startNode of impl.nodes.keys()) {
|
|
1927
2553
|
if (!discovered.has(startNode)) {
|
|
1928
2554
|
// Start BFS coloring from this component
|
|
1929
2555
|
const queue = [startNode];
|
|
@@ -1963,12 +2589,13 @@ export const isBipartite = graph => {
|
|
|
1963
2589
|
* For undirected graphs, we need to find the other endpoint of each edge incident to the node.
|
|
1964
2590
|
*/
|
|
1965
2591
|
const getUndirectedNeighbors = (graph, nodeIndex) => {
|
|
2592
|
+
const impl = graphImpl(graph);
|
|
1966
2593
|
const neighbors = new Set();
|
|
1967
2594
|
// Check edges where this node is the source
|
|
1968
|
-
const adjacencyList =
|
|
2595
|
+
const adjacencyList = impl.adjacency.get(nodeIndex);
|
|
1969
2596
|
if (adjacencyList !== undefined) {
|
|
1970
2597
|
for (const edgeIndex of adjacencyList) {
|
|
1971
|
-
const edge =
|
|
2598
|
+
const edge = impl.edges.get(edgeIndex);
|
|
1972
2599
|
if (edge !== undefined) {
|
|
1973
2600
|
// For undirected graphs, the neighbor is the other endpoint
|
|
1974
2601
|
const otherNode = edge.source === nodeIndex ? edge.target : edge.source;
|
|
@@ -1978,7 +2605,20 @@ const getUndirectedNeighbors = (graph, nodeIndex) => {
|
|
|
1978
2605
|
}
|
|
1979
2606
|
return Array.from(neighbors);
|
|
1980
2607
|
};
|
|
1981
|
-
const getTraversalNeighbors = (graph, nodeIndex, direction) =>
|
|
2608
|
+
const getTraversalNeighbors = (graph, nodeIndex, direction) => {
|
|
2609
|
+
if (graph.type === "undirected") {
|
|
2610
|
+
return getUndirectedNeighbors(graph, nodeIndex);
|
|
2611
|
+
}
|
|
2612
|
+
const directed = graph;
|
|
2613
|
+
if (direction !== "undirected") {
|
|
2614
|
+
return getDirectedNeighbors(directed, nodeIndex, direction);
|
|
2615
|
+
}
|
|
2616
|
+
const neighbors = new Set(getDirectedNeighbors(directed, nodeIndex, "outgoing"));
|
|
2617
|
+
for (const neighbor of getDirectedNeighbors(directed, nodeIndex, "incoming")) {
|
|
2618
|
+
neighbors.add(neighbor);
|
|
2619
|
+
}
|
|
2620
|
+
return Array.from(neighbors);
|
|
2621
|
+
};
|
|
1982
2622
|
const getTraversableNeighbor = (graph, current, edge) => graph.type === "undirected" && edge.target === current ? edge.source : edge.target;
|
|
1983
2623
|
/**
|
|
1984
2624
|
* Finds connected components in an undirected graph.
|
|
@@ -2006,9 +2646,10 @@ const getTraversableNeighbor = (graph, current, edge) => graph.type === "undirec
|
|
|
2006
2646
|
* @since 3.18.0
|
|
2007
2647
|
*/
|
|
2008
2648
|
export const connectedComponents = graph => {
|
|
2649
|
+
const impl = graphImpl(graph);
|
|
2009
2650
|
const visited = new Set();
|
|
2010
2651
|
const components = [];
|
|
2011
|
-
for (const startNode of
|
|
2652
|
+
for (const startNode of impl.nodes.keys()) {
|
|
2012
2653
|
if (!visited.has(startNode)) {
|
|
2013
2654
|
// DFS to find all nodes in this component
|
|
2014
2655
|
const component = [];
|
|
@@ -2067,9 +2708,10 @@ export const stronglyConnectedComponents = graph => {
|
|
|
2067
2708
|
message: "Cannot find strongly connected components of undirected graph"
|
|
2068
2709
|
});
|
|
2069
2710
|
}
|
|
2711
|
+
const impl = graphImpl(graph);
|
|
2070
2712
|
const visited = new Set();
|
|
2071
2713
|
const finishOrder = [];
|
|
2072
|
-
for (const startNode of
|
|
2714
|
+
for (const startNode of impl.nodes.keys()) {
|
|
2073
2715
|
if (visited.has(startNode)) {
|
|
2074
2716
|
continue;
|
|
2075
2717
|
}
|
|
@@ -2118,10 +2760,10 @@ export const stronglyConnectedComponents = graph => {
|
|
|
2118
2760
|
visited.add(node);
|
|
2119
2761
|
scc.push(node);
|
|
2120
2762
|
// Use reverse adjacency (transpose graph)
|
|
2121
|
-
const reverseAdjacency =
|
|
2763
|
+
const reverseAdjacency = impl.reverseAdjacency.get(node);
|
|
2122
2764
|
if (reverseAdjacency !== undefined) {
|
|
2123
2765
|
for (const edgeIndex of reverseAdjacency) {
|
|
2124
|
-
const edge =
|
|
2766
|
+
const edge = impl.edges.get(edgeIndex);
|
|
2125
2767
|
if (edge !== undefined) {
|
|
2126
2768
|
const predecessor = edge.source;
|
|
2127
2769
|
if (!visited.has(predecessor)) {
|
|
@@ -2135,18 +2777,42 @@ export const stronglyConnectedComponents = graph => {
|
|
|
2135
2777
|
}
|
|
2136
2778
|
return sccs;
|
|
2137
2779
|
};
|
|
2138
|
-
const
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2780
|
+
const minHeapLessThan = (self, that) => self.priority < that.priority || self.priority === that.priority && self.sequence < that.sequence;
|
|
2781
|
+
const minHeapPush = (heap, entry) => {
|
|
2782
|
+
let index = heap.length;
|
|
2783
|
+
heap.push(entry);
|
|
2784
|
+
while (index > 0) {
|
|
2785
|
+
const parent = index - 1 >>> 1;
|
|
2786
|
+
if (!minHeapLessThan(entry, heap[parent])) {
|
|
2787
|
+
break;
|
|
2146
2788
|
}
|
|
2147
|
-
|
|
2789
|
+
heap[index] = heap[parent];
|
|
2790
|
+
index = parent;
|
|
2791
|
+
}
|
|
2792
|
+
heap[index] = entry;
|
|
2793
|
+
};
|
|
2794
|
+
const minHeapPop = heap => {
|
|
2795
|
+
const first = heap[0];
|
|
2796
|
+
const last = heap.pop();
|
|
2797
|
+
if (last === undefined || heap.length === 0) {
|
|
2798
|
+
return first;
|
|
2799
|
+
}
|
|
2800
|
+
let index = 0;
|
|
2801
|
+
while (true) {
|
|
2802
|
+
const left = index * 2 + 1;
|
|
2803
|
+
if (left >= heap.length) {
|
|
2804
|
+
break;
|
|
2805
|
+
}
|
|
2806
|
+
const right = left + 1;
|
|
2807
|
+
const child = right < heap.length && minHeapLessThan(heap[right], heap[left]) ? right : left;
|
|
2808
|
+
if (!minHeapLessThan(heap[child], last)) {
|
|
2809
|
+
break;
|
|
2810
|
+
}
|
|
2811
|
+
heap[index] = heap[child];
|
|
2812
|
+
index = child;
|
|
2148
2813
|
}
|
|
2149
|
-
|
|
2814
|
+
heap[index] = last;
|
|
2815
|
+
return first;
|
|
2150
2816
|
};
|
|
2151
2817
|
/**
|
|
2152
2818
|
* Finds the shortest path from the configured source node to the target node
|
|
@@ -2154,9 +2820,10 @@ const validateNonNegativeEdgeWeights = (graph, cost, algorithm) => {
|
|
|
2154
2820
|
*
|
|
2155
2821
|
* **Details**
|
|
2156
2822
|
*
|
|
2157
|
-
* Edge costs must be non-negative
|
|
2158
|
-
*
|
|
2159
|
-
*
|
|
2823
|
+
* Edge costs must be non-negative and not `NaN`. `Infinity` is allowed and
|
|
2824
|
+
* behaves like an impassable edge. Returns `Option.none()` when the target is
|
|
2825
|
+
* not reachable, and throws a `GraphError` when either endpoint is missing or an
|
|
2826
|
+
* edge cost is negative or `NaN`.
|
|
2160
2827
|
*
|
|
2161
2828
|
* **Example** (Finding shortest paths with Dijkstra)
|
|
2162
2829
|
*
|
|
@@ -2188,14 +2855,24 @@ const validateNonNegativeEdgeWeights = (graph, cost, algorithm) => {
|
|
|
2188
2855
|
* @since 3.18.0
|
|
2189
2856
|
*/
|
|
2190
2857
|
export const dijkstra = /*#__PURE__*/dual(2, (graph, config) => {
|
|
2858
|
+
const impl = graphImpl(graph);
|
|
2191
2859
|
// Validate that source and target nodes exist
|
|
2192
|
-
if (!
|
|
2860
|
+
if (!impl.nodes.has(config.source)) {
|
|
2193
2861
|
throw missingNode(config.source);
|
|
2194
2862
|
}
|
|
2195
|
-
if (!
|
|
2863
|
+
if (!impl.nodes.has(config.target)) {
|
|
2196
2864
|
throw missingNode(config.target);
|
|
2197
2865
|
}
|
|
2198
|
-
const edgeWeights =
|
|
2866
|
+
const edgeWeights = new Map();
|
|
2867
|
+
for (const [edgeIndex, edgeData] of impl.edges) {
|
|
2868
|
+
const weight = config.cost(edgeData.data);
|
|
2869
|
+
if (Number.isNaN(weight) || weight < 0) {
|
|
2870
|
+
throw new GraphError({
|
|
2871
|
+
message: "Dijkstra's algorithm requires non-negative edge weights"
|
|
2872
|
+
});
|
|
2873
|
+
}
|
|
2874
|
+
edgeWeights.set(edgeIndex, weight);
|
|
2875
|
+
}
|
|
2199
2876
|
// Early return if source equals target
|
|
2200
2877
|
if (config.source === config.target) {
|
|
2201
2878
|
return Option.some({
|
|
@@ -2210,24 +2887,19 @@ export const dijkstra = /*#__PURE__*/dual(2, (graph, config) => {
|
|
|
2210
2887
|
const visited = new Set();
|
|
2211
2888
|
// Initialize distances
|
|
2212
2889
|
// Iterate directly over node keys
|
|
2213
|
-
for (const node of
|
|
2890
|
+
for (const node of impl.nodes.keys()) {
|
|
2214
2891
|
distances.set(node, node === config.source ? 0 : Infinity);
|
|
2215
2892
|
previous.set(node, null);
|
|
2216
2893
|
}
|
|
2217
|
-
|
|
2218
|
-
|
|
2894
|
+
const priorityQueue = [];
|
|
2895
|
+
let sequence = 0;
|
|
2896
|
+
minHeapPush(priorityQueue, {
|
|
2219
2897
|
node: config.source,
|
|
2220
|
-
|
|
2221
|
-
|
|
2898
|
+
priority: 0,
|
|
2899
|
+
sequence: sequence++
|
|
2900
|
+
});
|
|
2222
2901
|
while (priorityQueue.length > 0) {
|
|
2223
|
-
|
|
2224
|
-
let minIndex = 0;
|
|
2225
|
-
for (let i = 1; i < priorityQueue.length; i++) {
|
|
2226
|
-
if (priorityQueue[i].distance < priorityQueue[minIndex].distance) {
|
|
2227
|
-
minIndex = i;
|
|
2228
|
-
}
|
|
2229
|
-
}
|
|
2230
|
-
const current = priorityQueue.splice(minIndex, 1)[0];
|
|
2902
|
+
const current = minHeapPop(priorityQueue);
|
|
2231
2903
|
const currentNode = current.node;
|
|
2232
2904
|
// Skip if already visited (can happen with duplicate entries)
|
|
2233
2905
|
if (visited.has(currentNode)) {
|
|
@@ -2241,10 +2913,10 @@ export const dijkstra = /*#__PURE__*/dual(2, (graph, config) => {
|
|
|
2241
2913
|
// Get current distance
|
|
2242
2914
|
const currentDistance = distances.get(currentNode);
|
|
2243
2915
|
// Examine all outgoing edges
|
|
2244
|
-
const adjacencyList =
|
|
2916
|
+
const adjacencyList = impl.adjacency.get(currentNode);
|
|
2245
2917
|
if (adjacencyList !== undefined) {
|
|
2246
2918
|
for (const edgeIndex of adjacencyList) {
|
|
2247
|
-
const edge =
|
|
2919
|
+
const edge = impl.edges.get(edgeIndex);
|
|
2248
2920
|
if (edge !== undefined) {
|
|
2249
2921
|
const neighbor = getTraversableNeighbor(graph, currentNode, edge);
|
|
2250
2922
|
const cost = edgeWeights.get(edgeIndex);
|
|
@@ -2259,9 +2931,10 @@ export const dijkstra = /*#__PURE__*/dual(2, (graph, config) => {
|
|
|
2259
2931
|
});
|
|
2260
2932
|
// Add to priority queue if not visited
|
|
2261
2933
|
if (!visited.has(neighbor)) {
|
|
2262
|
-
priorityQueue
|
|
2934
|
+
minHeapPush(priorityQueue, {
|
|
2263
2935
|
node: neighbor,
|
|
2264
|
-
|
|
2936
|
+
priority: newDistance,
|
|
2937
|
+
sequence: sequence++
|
|
2265
2938
|
});
|
|
2266
2939
|
}
|
|
2267
2940
|
}
|
|
@@ -2301,8 +2974,9 @@ export const dijkstra = /*#__PURE__*/dual(2, (graph, config) => {
|
|
|
2301
2974
|
* **Details**
|
|
2302
2975
|
*
|
|
2303
2976
|
* Computes distances, reconstructed node paths, and edge-data paths for every
|
|
2304
|
-
* source and target pair in O(V^3) time. Negative edge weights are allowed,
|
|
2305
|
-
*
|
|
2977
|
+
* source and target pair in O(V^3) time. Negative edge weights are allowed, and
|
|
2978
|
+
* `Infinity` behaves like an impassable edge. A `GraphError` is thrown if any
|
|
2979
|
+
* edge weight is `NaN` or `-Infinity`, or if any negative cycle is detected.
|
|
2306
2980
|
*
|
|
2307
2981
|
* **Example** (Finding all-pairs shortest paths)
|
|
2308
2982
|
*
|
|
@@ -2327,8 +3001,9 @@ export const dijkstra = /*#__PURE__*/dual(2, (graph, config) => {
|
|
|
2327
3001
|
* @since 3.18.0
|
|
2328
3002
|
*/
|
|
2329
3003
|
export const floydWarshall = /*#__PURE__*/dual(2, (graph, cost) => {
|
|
3004
|
+
const impl = graphImpl(graph);
|
|
2330
3005
|
// Get all nodes for Floyd-Warshall algorithm (needs array for nested iteration)
|
|
2331
|
-
const allNodes = Array.from(
|
|
3006
|
+
const allNodes = Array.from(impl.nodes.keys());
|
|
2332
3007
|
// Initialize distance matrix
|
|
2333
3008
|
const distances = new Map();
|
|
2334
3009
|
const next = new Map();
|
|
@@ -2340,13 +3015,16 @@ export const floydWarshall = /*#__PURE__*/dual(2, (graph, cost) => {
|
|
|
2340
3015
|
edgeMatrix.set(i, new Map());
|
|
2341
3016
|
for (const j of allNodes) {
|
|
2342
3017
|
distances.get(i).set(j, i === j ? 0 : Infinity);
|
|
2343
|
-
next.get(i).set(j, null);
|
|
2344
|
-
edgeMatrix.get(i).set(j, null);
|
|
2345
3018
|
}
|
|
2346
3019
|
}
|
|
2347
3020
|
// Set edge weights
|
|
2348
|
-
for (const [, edgeData] of
|
|
3021
|
+
for (const [, edgeData] of impl.edges) {
|
|
2349
3022
|
const weight = cost(edgeData.data);
|
|
3023
|
+
if (Number.isNaN(weight) || weight === -Infinity) {
|
|
3024
|
+
throw new GraphError({
|
|
3025
|
+
message: "Floyd-Warshall algorithm does not support NaN or -Infinity edge weights"
|
|
3026
|
+
});
|
|
3027
|
+
}
|
|
2350
3028
|
const i = edgeData.source;
|
|
2351
3029
|
const j = edgeData.target;
|
|
2352
3030
|
// Use minimum weight if multiple edges exist
|
|
@@ -2373,8 +3051,11 @@ export const floydWarshall = /*#__PURE__*/dual(2, (graph, cost) => {
|
|
|
2373
3051
|
const distKJ = distances.get(k).get(j);
|
|
2374
3052
|
const distIJ = distances.get(i).get(j);
|
|
2375
3053
|
if (distIK !== Infinity && distKJ !== Infinity && distIK + distKJ < distIJ) {
|
|
2376
|
-
|
|
2377
|
-
|
|
3054
|
+
const nextIK = next.get(i).get(k);
|
|
3055
|
+
if (nextIK !== undefined) {
|
|
3056
|
+
distances.get(i).set(j, distIK + distKJ);
|
|
3057
|
+
next.get(i).set(j, nextIK);
|
|
3058
|
+
}
|
|
2378
3059
|
}
|
|
2379
3060
|
}
|
|
2380
3061
|
}
|
|
@@ -2408,10 +3089,10 @@ export const floydWarshall = /*#__PURE__*/dual(2, (graph, cost) => {
|
|
|
2408
3089
|
path.push(current);
|
|
2409
3090
|
while (current !== j) {
|
|
2410
3091
|
const nextNode = next.get(current).get(j);
|
|
2411
|
-
if (nextNode ===
|
|
2412
|
-
const
|
|
2413
|
-
if (
|
|
2414
|
-
weights.push(
|
|
3092
|
+
if (nextNode === undefined) break;
|
|
3093
|
+
const edgeRow = edgeMatrix.get(current);
|
|
3094
|
+
if (edgeRow.has(nextNode)) {
|
|
3095
|
+
weights.push(edgeRow.get(nextNode));
|
|
2415
3096
|
}
|
|
2416
3097
|
current = nextNode;
|
|
2417
3098
|
path.push(current);
|
|
@@ -2433,10 +3114,11 @@ export const floydWarshall = /*#__PURE__*/dual(2, (graph, cost) => {
|
|
|
2433
3114
|
*
|
|
2434
3115
|
* **Details**
|
|
2435
3116
|
*
|
|
2436
|
-
* The edge-cost function must return non-negative weights
|
|
3117
|
+
* The edge-cost function must return non-negative weights and not `NaN`.
|
|
3118
|
+
* `Infinity` is allowed and behaves like an impassable edge. The heuristic
|
|
2437
3119
|
* should be consistent to preserve shortest-path guarantees. Returns
|
|
2438
3120
|
* `Option.none()` when the target is not reachable, and throws a `GraphError`
|
|
2439
|
-
* when either endpoint is missing or
|
|
3121
|
+
* when either endpoint is missing or an edge cost is negative or `NaN`.
|
|
2440
3122
|
*
|
|
2441
3123
|
* **Example** (Finding shortest paths with A-star)
|
|
2442
3124
|
*
|
|
@@ -2474,14 +3156,24 @@ export const floydWarshall = /*#__PURE__*/dual(2, (graph, cost) => {
|
|
|
2474
3156
|
* @since 3.18.0
|
|
2475
3157
|
*/
|
|
2476
3158
|
export const astar = /*#__PURE__*/dual(2, (graph, config) => {
|
|
3159
|
+
const impl = graphImpl(graph);
|
|
2477
3160
|
// Validate that source and target nodes exist
|
|
2478
|
-
if (!
|
|
3161
|
+
if (!impl.nodes.has(config.source)) {
|
|
2479
3162
|
throw missingNode(config.source);
|
|
2480
3163
|
}
|
|
2481
|
-
if (!
|
|
3164
|
+
if (!impl.nodes.has(config.target)) {
|
|
2482
3165
|
throw missingNode(config.target);
|
|
2483
3166
|
}
|
|
2484
|
-
const edgeWeights =
|
|
3167
|
+
const edgeWeights = new Map();
|
|
3168
|
+
for (const [edgeIndex, edgeData] of impl.edges) {
|
|
3169
|
+
const weight = config.cost(edgeData.data);
|
|
3170
|
+
if (Number.isNaN(weight) || weight < 0) {
|
|
3171
|
+
throw new GraphError({
|
|
3172
|
+
message: "A* algorithm requires non-negative edge weights"
|
|
3173
|
+
});
|
|
3174
|
+
}
|
|
3175
|
+
edgeWeights.set(edgeIndex, weight);
|
|
3176
|
+
}
|
|
2485
3177
|
// Early return if source equals target
|
|
2486
3178
|
if (config.source === config.target) {
|
|
2487
3179
|
return Option.some({
|
|
@@ -2504,7 +3196,7 @@ export const astar = /*#__PURE__*/dual(2, (graph, config) => {
|
|
|
2504
3196
|
const visited = new Set();
|
|
2505
3197
|
// Initialize scores
|
|
2506
3198
|
// Iterate directly over node keys
|
|
2507
|
-
for (const node of
|
|
3199
|
+
for (const node of impl.nodes.keys()) {
|
|
2508
3200
|
gScore.set(node, node === config.source ? 0 : Infinity);
|
|
2509
3201
|
fScore.set(node, Infinity);
|
|
2510
3202
|
previous.set(node, null);
|
|
@@ -2515,20 +3207,15 @@ export const astar = /*#__PURE__*/dual(2, (graph, config) => {
|
|
|
2515
3207
|
const h = config.heuristic(sourceNodeData.value, targetNodeData.value);
|
|
2516
3208
|
fScore.set(config.source, h);
|
|
2517
3209
|
}
|
|
2518
|
-
|
|
2519
|
-
|
|
3210
|
+
const openSet = [];
|
|
3211
|
+
let sequence = 0;
|
|
3212
|
+
minHeapPush(openSet, {
|
|
2520
3213
|
node: config.source,
|
|
2521
|
-
|
|
2522
|
-
|
|
3214
|
+
priority: fScore.get(config.source),
|
|
3215
|
+
sequence: sequence++
|
|
3216
|
+
});
|
|
2523
3217
|
while (openSet.length > 0) {
|
|
2524
|
-
|
|
2525
|
-
let minIndex = 0;
|
|
2526
|
-
for (let i = 1; i < openSet.length; i++) {
|
|
2527
|
-
if (openSet[i].fScore < openSet[minIndex].fScore) {
|
|
2528
|
-
minIndex = i;
|
|
2529
|
-
}
|
|
2530
|
-
}
|
|
2531
|
-
const current = openSet.splice(minIndex, 1)[0];
|
|
3218
|
+
const current = minHeapPop(openSet);
|
|
2532
3219
|
const currentNode = current.node;
|
|
2533
3220
|
// Skip if already visited
|
|
2534
3221
|
if (visited.has(currentNode)) {
|
|
@@ -2542,10 +3229,10 @@ export const astar = /*#__PURE__*/dual(2, (graph, config) => {
|
|
|
2542
3229
|
// Get current g-score
|
|
2543
3230
|
const currentGScore = gScore.get(currentNode);
|
|
2544
3231
|
// Examine all outgoing edges
|
|
2545
|
-
const adjacencyList =
|
|
3232
|
+
const adjacencyList = impl.adjacency.get(currentNode);
|
|
2546
3233
|
if (adjacencyList !== undefined) {
|
|
2547
3234
|
for (const edgeIndex of adjacencyList) {
|
|
2548
|
-
const edge =
|
|
3235
|
+
const edge = impl.edges.get(edgeIndex);
|
|
2549
3236
|
if (edge !== undefined) {
|
|
2550
3237
|
const neighbor = getTraversableNeighbor(graph, currentNode, edge);
|
|
2551
3238
|
const weight = edgeWeights.get(edgeIndex);
|
|
@@ -2567,9 +3254,10 @@ export const astar = /*#__PURE__*/dual(2, (graph, config) => {
|
|
|
2567
3254
|
fScore.set(neighbor, f);
|
|
2568
3255
|
// Add to open set if not visited
|
|
2569
3256
|
if (!visited.has(neighbor)) {
|
|
2570
|
-
openSet
|
|
3257
|
+
minHeapPush(openSet, {
|
|
2571
3258
|
node: neighbor,
|
|
2572
|
-
|
|
3259
|
+
priority: f,
|
|
3260
|
+
sequence: sequence++
|
|
2573
3261
|
});
|
|
2574
3262
|
}
|
|
2575
3263
|
}
|
|
@@ -2609,9 +3297,10 @@ export const astar = /*#__PURE__*/dual(2, (graph, config) => {
|
|
|
2609
3297
|
*
|
|
2610
3298
|
* **Details**
|
|
2611
3299
|
*
|
|
2612
|
-
* Negative edge weights are allowed
|
|
2613
|
-
*
|
|
2614
|
-
*
|
|
3300
|
+
* Negative edge weights are allowed, and `Infinity` behaves like an impassable
|
|
3301
|
+
* edge. Returns `Option.none()` when the target is unreachable or when a
|
|
3302
|
+
* negative cycle affects the path to the target. Throws a `GraphError` when
|
|
3303
|
+
* either endpoint is missing or an edge weight is `NaN` or `-Infinity`.
|
|
2615
3304
|
*
|
|
2616
3305
|
* **Example** (Finding shortest paths with Bellman-Ford)
|
|
2617
3306
|
*
|
|
@@ -2643,33 +3332,31 @@ export const astar = /*#__PURE__*/dual(2, (graph, config) => {
|
|
|
2643
3332
|
* @since 3.18.0
|
|
2644
3333
|
*/
|
|
2645
3334
|
export const bellmanFord = /*#__PURE__*/dual(2, (graph, config) => {
|
|
3335
|
+
const impl = graphImpl(graph);
|
|
2646
3336
|
// Validate that source and target nodes exist
|
|
2647
|
-
if (!
|
|
3337
|
+
if (!impl.nodes.has(config.source)) {
|
|
2648
3338
|
throw missingNode(config.source);
|
|
2649
3339
|
}
|
|
2650
|
-
if (!
|
|
3340
|
+
if (!impl.nodes.has(config.target)) {
|
|
2651
3341
|
throw missingNode(config.target);
|
|
2652
3342
|
}
|
|
2653
|
-
// Early return if source equals target
|
|
2654
|
-
if (config.source === config.target) {
|
|
2655
|
-
return Option.some({
|
|
2656
|
-
path: [config.source],
|
|
2657
|
-
distance: 0,
|
|
2658
|
-
costs: []
|
|
2659
|
-
});
|
|
2660
|
-
}
|
|
2661
3343
|
// Initialize distances and predecessors
|
|
2662
3344
|
const distances = new Map();
|
|
2663
3345
|
const previous = new Map();
|
|
2664
3346
|
// Iterate directly over node keys
|
|
2665
|
-
for (const node of
|
|
3347
|
+
for (const node of impl.nodes.keys()) {
|
|
2666
3348
|
distances.set(node, node === config.source ? 0 : Infinity);
|
|
2667
3349
|
previous.set(node, null);
|
|
2668
3350
|
}
|
|
2669
3351
|
// Collect all edges for relaxation
|
|
2670
3352
|
const edges = [];
|
|
2671
|
-
for (const [, edgeData] of
|
|
3353
|
+
for (const [, edgeData] of impl.edges) {
|
|
2672
3354
|
const weight = config.cost(edgeData.data);
|
|
3355
|
+
if (Number.isNaN(weight) || weight === -Infinity) {
|
|
3356
|
+
throw new GraphError({
|
|
3357
|
+
message: "Bellman-Ford algorithm does not support NaN or -Infinity edge weights"
|
|
3358
|
+
});
|
|
3359
|
+
}
|
|
2673
3360
|
edges.push({
|
|
2674
3361
|
source: edgeData.source,
|
|
2675
3362
|
target: edgeData.target,
|
|
@@ -2686,7 +3373,7 @@ export const bellmanFord = /*#__PURE__*/dual(2, (graph, config) => {
|
|
|
2686
3373
|
}
|
|
2687
3374
|
}
|
|
2688
3375
|
// Relax edges up to V-1 times
|
|
2689
|
-
const nodeCount =
|
|
3376
|
+
const nodeCount = impl.nodes.size;
|
|
2690
3377
|
for (let i = 0; i < nodeCount - 1; i++) {
|
|
2691
3378
|
let hasUpdate = false;
|
|
2692
3379
|
for (const edge of edges) {
|
|
@@ -2868,7 +3555,7 @@ export class Walker {
|
|
|
2868
3555
|
*/
|
|
2869
3556
|
visit) {
|
|
2870
3557
|
this.visit = visit;
|
|
2871
|
-
this[Symbol.iterator] = visit((index, data) => [index, data])[Symbol.iterator];
|
|
3558
|
+
this[Symbol.iterator] = () => visit((index, data) => [index, data])[Symbol.iterator]();
|
|
2872
3559
|
}
|
|
2873
3560
|
}
|
|
2874
3561
|
/**
|
|
@@ -2947,7 +3634,8 @@ export const entries = walker => walker.visit((index, data) => [index, data]);
|
|
|
2947
3634
|
* **Details**
|
|
2948
3635
|
*
|
|
2949
3636
|
* If no start nodes are supplied, the iterator is empty. The `direction` option
|
|
2950
|
-
* chooses whether to follow outgoing or incoming edges.
|
|
3637
|
+
* chooses whether to follow outgoing or incoming edges. The `radius` option
|
|
3638
|
+
* limits traversal by edge distance from the start nodes. Throws a `GraphError`
|
|
2951
3639
|
* if any configured start node does not exist.
|
|
2952
3640
|
*
|
|
2953
3641
|
* **Example** (Traversing depth-first)
|
|
@@ -2980,6 +3668,7 @@ export const entries = walker => walker.visit((index, data) => [index, data]);
|
|
|
2980
3668
|
export const dfs = /*#__PURE__*/dual(args => isGraph(args[0]), (graph, config = {}) => {
|
|
2981
3669
|
const start = config.start ?? [];
|
|
2982
3670
|
const direction = config.direction ?? "outgoing";
|
|
3671
|
+
const radius = config.radius ?? Infinity;
|
|
2983
3672
|
// Validate that all start nodes exist
|
|
2984
3673
|
for (const nodeIndex of start) {
|
|
2985
3674
|
if (!hasNode(graph, nodeIndex)) {
|
|
@@ -2988,26 +3677,61 @@ export const dfs = /*#__PURE__*/dual(args => isGraph(args[0]), (graph, config =
|
|
|
2988
3677
|
}
|
|
2989
3678
|
return new Walker(f => ({
|
|
2990
3679
|
[Symbol.iterator]: () => {
|
|
2991
|
-
const
|
|
2992
|
-
const
|
|
3680
|
+
const depths = radius === Infinity ? undefined : new Map();
|
|
3681
|
+
const stack = [];
|
|
3682
|
+
const yielded = new Set();
|
|
3683
|
+
if (depths === undefined) {
|
|
3684
|
+
for (const node of start) {
|
|
3685
|
+
stack.push([node, 0]);
|
|
3686
|
+
}
|
|
3687
|
+
} else {
|
|
3688
|
+
const starts = new Set();
|
|
3689
|
+
for (let i = start.length - 1; i >= 0; i--) {
|
|
3690
|
+
const node = start[i];
|
|
3691
|
+
if (!starts.has(node)) {
|
|
3692
|
+
starts.add(node);
|
|
3693
|
+
stack.push([node, 0]);
|
|
3694
|
+
depths.set(node, 0);
|
|
3695
|
+
}
|
|
3696
|
+
}
|
|
3697
|
+
stack.reverse();
|
|
3698
|
+
}
|
|
2993
3699
|
const nextMapped = () => {
|
|
2994
3700
|
while (stack.length > 0) {
|
|
2995
|
-
const current = stack.pop();
|
|
2996
|
-
if (
|
|
3701
|
+
const [current, depth] = stack.pop();
|
|
3702
|
+
if (depths === undefined) {
|
|
3703
|
+
if (yielded.has(current)) {
|
|
3704
|
+
continue;
|
|
3705
|
+
}
|
|
3706
|
+
} else if (depths.get(current) !== depth) {
|
|
2997
3707
|
continue;
|
|
2998
3708
|
}
|
|
2999
|
-
discovered.add(current);
|
|
3000
3709
|
const nodeDataOption = getNode(graph, current);
|
|
3001
3710
|
if (Option.isNone(nodeDataOption)) {
|
|
3002
3711
|
continue;
|
|
3003
3712
|
}
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3713
|
+
if (depth < radius) {
|
|
3714
|
+
const neighbors = getTraversalNeighbors(graph, current, direction);
|
|
3715
|
+
for (let i = neighbors.length - 1; i >= 0; i--) {
|
|
3716
|
+
const neighbor = neighbors[i];
|
|
3717
|
+
const nextDepth = depth + 1;
|
|
3718
|
+
if (depths === undefined) {
|
|
3719
|
+
if (!yielded.has(neighbor)) {
|
|
3720
|
+
stack.push([neighbor, nextDepth]);
|
|
3721
|
+
}
|
|
3722
|
+
continue;
|
|
3723
|
+
}
|
|
3724
|
+
const neighborDepth = depths.get(neighbor);
|
|
3725
|
+
if (neighborDepth === undefined || nextDepth < neighborDepth) {
|
|
3726
|
+
depths.set(neighbor, nextDepth);
|
|
3727
|
+
stack.push([neighbor, nextDepth]);
|
|
3728
|
+
}
|
|
3009
3729
|
}
|
|
3010
3730
|
}
|
|
3731
|
+
if (yielded.has(current)) {
|
|
3732
|
+
continue;
|
|
3733
|
+
}
|
|
3734
|
+
yielded.add(current);
|
|
3011
3735
|
return {
|
|
3012
3736
|
done: false,
|
|
3013
3737
|
value: f(current, nodeDataOption.value)
|
|
@@ -3031,7 +3755,8 @@ export const dfs = /*#__PURE__*/dual(args => isGraph(args[0]), (graph, config =
|
|
|
3031
3755
|
* **Details**
|
|
3032
3756
|
*
|
|
3033
3757
|
* If no start nodes are supplied, the iterator is empty. The `direction` option
|
|
3034
|
-
* chooses whether to follow outgoing or incoming edges.
|
|
3758
|
+
* chooses whether to follow outgoing or incoming edges. The `radius` option
|
|
3759
|
+
* limits traversal by edge distance from the start nodes. Throws a `GraphError`
|
|
3035
3760
|
* if any configured start node does not exist.
|
|
3036
3761
|
*
|
|
3037
3762
|
* **Example** (Traversing breadth-first)
|
|
@@ -3064,6 +3789,7 @@ export const dfs = /*#__PURE__*/dual(args => isGraph(args[0]), (graph, config =
|
|
|
3064
3789
|
export const bfs = /*#__PURE__*/dual(args => isGraph(args[0]), (graph, config = {}) => {
|
|
3065
3790
|
const start = config.start ?? [];
|
|
3066
3791
|
const direction = config.direction ?? "outgoing";
|
|
3792
|
+
const radius = config.radius ?? Infinity;
|
|
3067
3793
|
// Validate that all start nodes exist
|
|
3068
3794
|
for (const nodeIndex of start) {
|
|
3069
3795
|
if (!hasNode(graph, nodeIndex)) {
|
|
@@ -3072,17 +3798,19 @@ export const bfs = /*#__PURE__*/dual(args => isGraph(args[0]), (graph, config =
|
|
|
3072
3798
|
}
|
|
3073
3799
|
return new Walker(f => ({
|
|
3074
3800
|
[Symbol.iterator]: () => {
|
|
3075
|
-
const queue = [
|
|
3801
|
+
const queue = start.map(node => [node, 0]);
|
|
3076
3802
|
const discovered = new Set();
|
|
3077
3803
|
const nextMapped = () => {
|
|
3078
3804
|
while (queue.length > 0) {
|
|
3079
|
-
const current = queue.shift();
|
|
3805
|
+
const [current, depth] = queue.shift();
|
|
3080
3806
|
if (!discovered.has(current)) {
|
|
3081
3807
|
discovered.add(current);
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3808
|
+
if (depth < radius) {
|
|
3809
|
+
const neighbors = getTraversalNeighbors(graph, current, direction);
|
|
3810
|
+
for (const neighbor of neighbors) {
|
|
3811
|
+
if (!discovered.has(neighbor)) {
|
|
3812
|
+
queue.push([neighbor, depth + 1]);
|
|
3813
|
+
}
|
|
3086
3814
|
}
|
|
3087
3815
|
}
|
|
3088
3816
|
const nodeData = getNode(graph, current);
|
|
@@ -3092,7 +3820,7 @@ export const bfs = /*#__PURE__*/dual(args => isGraph(args[0]), (graph, config =
|
|
|
3092
3820
|
value: f(current, nodeData.value)
|
|
3093
3821
|
};
|
|
3094
3822
|
}
|
|
3095
|
-
|
|
3823
|
+
continue;
|
|
3096
3824
|
}
|
|
3097
3825
|
}
|
|
3098
3826
|
return {
|
|
@@ -3173,17 +3901,18 @@ export const topo = /*#__PURE__*/dual(args => isGraph(args[0]), (graph, config =
|
|
|
3173
3901
|
}
|
|
3174
3902
|
return new Walker(f => ({
|
|
3175
3903
|
[Symbol.iterator]: () => {
|
|
3904
|
+
const impl = graphImpl(graph);
|
|
3176
3905
|
const inDegree = new Map();
|
|
3177
3906
|
const remaining = new Set();
|
|
3178
3907
|
const initialSet = new Set(initials);
|
|
3179
3908
|
const queue = [...initials];
|
|
3180
3909
|
// Initialize in-degree counts
|
|
3181
|
-
for (const [nodeIndex] of
|
|
3910
|
+
for (const [nodeIndex] of impl.nodes) {
|
|
3182
3911
|
inDegree.set(nodeIndex, 0);
|
|
3183
3912
|
remaining.add(nodeIndex);
|
|
3184
3913
|
}
|
|
3185
3914
|
// Calculate in-degrees
|
|
3186
|
-
for (const [, edgeData] of
|
|
3915
|
+
for (const [, edgeData] of impl.edges) {
|
|
3187
3916
|
const currentInDegree = inDegree.get(edgeData.target) || 0;
|
|
3188
3917
|
inDegree.set(edgeData.target, currentInDegree + 1);
|
|
3189
3918
|
}
|
|
@@ -3225,9 +3954,14 @@ export const topo = /*#__PURE__*/dual(args => isGraph(args[0]), (graph, config =
|
|
|
3225
3954
|
value: f(current, nodeData.value)
|
|
3226
3955
|
};
|
|
3227
3956
|
}
|
|
3228
|
-
|
|
3957
|
+
continue;
|
|
3229
3958
|
}
|
|
3230
3959
|
}
|
|
3960
|
+
if (remaining.size > 0) {
|
|
3961
|
+
throw new GraphError({
|
|
3962
|
+
message: "Cannot perform topological sort on cyclic graph"
|
|
3963
|
+
});
|
|
3964
|
+
}
|
|
3231
3965
|
return {
|
|
3232
3966
|
done: true,
|
|
3233
3967
|
value: undefined
|
|
@@ -3247,7 +3981,14 @@ export const topo = /*#__PURE__*/dual(args => isGraph(args[0]), (graph, config =
|
|
|
3247
3981
|
*
|
|
3248
3982
|
* Nodes are emitted after their reachable descendants have been processed. If
|
|
3249
3983
|
* no start nodes are supplied, the iterator is empty. The `direction` option
|
|
3250
|
-
* chooses whether to follow outgoing or incoming edges.
|
|
3984
|
+
* chooses whether to follow outgoing or incoming edges. The `radius` option
|
|
3985
|
+
* limits traversal by edge distance from the start nodes.
|
|
3986
|
+
*
|
|
3987
|
+
* **Gotchas**
|
|
3988
|
+
*
|
|
3989
|
+
* With a finite `radius`, iteration first performs a bounded breadth-first
|
|
3990
|
+
* traversal to determine shortest-distance membership before emitting nodes in
|
|
3991
|
+
* postorder.
|
|
3251
3992
|
*
|
|
3252
3993
|
* **Example** (Traversing in postorder)
|
|
3253
3994
|
*
|
|
@@ -3275,6 +4016,7 @@ export const topo = /*#__PURE__*/dual(args => isGraph(args[0]), (graph, config =
|
|
|
3275
4016
|
export const dfsPostOrder = /*#__PURE__*/dual(args => isGraph(args[0]), (graph, config = {}) => {
|
|
3276
4017
|
const start = config.start ?? [];
|
|
3277
4018
|
const direction = config.direction ?? "outgoing";
|
|
4019
|
+
const radius = config.radius ?? Infinity;
|
|
3278
4020
|
// Validate that all start nodes exist
|
|
3279
4021
|
for (const nodeIndex of start) {
|
|
3280
4022
|
if (!hasNode(graph, nodeIndex)) {
|
|
@@ -3283,6 +4025,11 @@ export const dfsPostOrder = /*#__PURE__*/dual(args => isGraph(args[0]), (graph,
|
|
|
3283
4025
|
}
|
|
3284
4026
|
return new Walker(f => ({
|
|
3285
4027
|
[Symbol.iterator]: () => {
|
|
4028
|
+
const reached = radius === Infinity ? undefined : new Set(indices(bfs(graph, {
|
|
4029
|
+
start,
|
|
4030
|
+
direction,
|
|
4031
|
+
radius
|
|
4032
|
+
})));
|
|
3286
4033
|
const stack = [];
|
|
3287
4034
|
const discovered = new Set();
|
|
3288
4035
|
const finished = new Set();
|
|
@@ -3305,7 +4052,7 @@ export const dfsPostOrder = /*#__PURE__*/dual(args => isGraph(args[0]), (graph,
|
|
|
3305
4052
|
const neighbors = getTraversalNeighbors(graph, current.node, direction);
|
|
3306
4053
|
for (let i = neighbors.length - 1; i >= 0; i--) {
|
|
3307
4054
|
const neighbor = neighbors[i];
|
|
3308
|
-
if (!discovered.has(neighbor) && !finished.has(neighbor)) {
|
|
4055
|
+
if ((reached === undefined || reached.has(neighbor)) && !discovered.has(neighbor) && !finished.has(neighbor)) {
|
|
3309
4056
|
stack.push({
|
|
3310
4057
|
node: neighbor,
|
|
3311
4058
|
visitedChildren: false
|
|
@@ -3323,7 +4070,7 @@ export const dfsPostOrder = /*#__PURE__*/dual(args => isGraph(args[0]), (graph,
|
|
|
3323
4070
|
value: f(nodeToEmit, nodeData.value)
|
|
3324
4071
|
};
|
|
3325
4072
|
}
|
|
3326
|
-
|
|
4073
|
+
continue;
|
|
3327
4074
|
}
|
|
3328
4075
|
}
|
|
3329
4076
|
}
|
|
@@ -3367,7 +4114,7 @@ export const dfsPostOrder = /*#__PURE__*/dual(args => isGraph(args[0]), (graph,
|
|
|
3367
4114
|
*/
|
|
3368
4115
|
export const nodes = graph => new Walker(f => ({
|
|
3369
4116
|
[Symbol.iterator]() {
|
|
3370
|
-
const nodeMap = graph.nodes;
|
|
4117
|
+
const nodeMap = graphImpl(graph).nodes;
|
|
3371
4118
|
const iterator = nodeMap.entries();
|
|
3372
4119
|
return {
|
|
3373
4120
|
next() {
|
|
@@ -3417,7 +4164,7 @@ export const nodes = graph => new Walker(f => ({
|
|
|
3417
4164
|
*/
|
|
3418
4165
|
export const edges = graph => new Walker(f => ({
|
|
3419
4166
|
[Symbol.iterator]() {
|
|
3420
|
-
const edgeMap = graph.edges;
|
|
4167
|
+
const edgeMap = graphImpl(graph).edges;
|
|
3421
4168
|
const iterator = edgeMap.entries();
|
|
3422
4169
|
return {
|
|
3423
4170
|
next() {
|
|
@@ -3481,8 +4228,9 @@ export const externals = /*#__PURE__*/dual(args => isGraph(args[0]), (graph, con
|
|
|
3481
4228
|
const direction = config.direction ?? "outgoing";
|
|
3482
4229
|
return new Walker(f => ({
|
|
3483
4230
|
[Symbol.iterator]: () => {
|
|
3484
|
-
const
|
|
3485
|
-
const
|
|
4231
|
+
const impl = graphImpl(graph);
|
|
4232
|
+
const nodeMap = impl.nodes;
|
|
4233
|
+
const adjacencyMap = direction === "incoming" ? impl.reverseAdjacency : impl.adjacency;
|
|
3486
4234
|
const nodeIterator = nodeMap.entries();
|
|
3487
4235
|
const nextMapped = () => {
|
|
3488
4236
|
let current = nodeIterator.next();
|