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/src/Graph.ts
CHANGED
|
@@ -15,11 +15,12 @@ import { dual } from "./Function.ts"
|
|
|
15
15
|
import * as Hash from "./Hash.ts"
|
|
16
16
|
import type { Inspectable } from "./Inspectable.ts"
|
|
17
17
|
import { NodeInspectSymbol } from "./Inspectable.ts"
|
|
18
|
+
import * as MutableHashMap from "./MutableHashMap.ts"
|
|
18
19
|
import * as Option from "./Option.ts"
|
|
19
20
|
import type { Pipeable } from "./Pipeable.ts"
|
|
20
21
|
import { pipeArguments } from "./Pipeable.ts"
|
|
21
22
|
import { hasProperty } from "./Predicate.ts"
|
|
22
|
-
import type { Mutable } from "./Types.ts"
|
|
23
|
+
import type { Covariant, Invariant, Mutable } from "./Types.ts"
|
|
23
24
|
|
|
24
25
|
const TypeId = "~effect/collections/Graph"
|
|
25
26
|
|
|
@@ -109,25 +110,19 @@ export class Edge<E> extends Data.Class<{
|
|
|
109
110
|
export type Kind = "directed" | "undirected"
|
|
110
111
|
|
|
111
112
|
/**
|
|
112
|
-
* Common
|
|
113
|
+
* Common public protocol for graph values.
|
|
113
114
|
*
|
|
114
115
|
* **Details**
|
|
115
116
|
*
|
|
116
|
-
* Contains the
|
|
117
|
-
*
|
|
117
|
+
* Contains only the runtime marker and shared protocols. Graph storage is kept
|
|
118
|
+
* internal; use module functions such as `nodes`, `edges`, `getNode`, and
|
|
119
|
+
* `getEdge` to inspect graph contents.
|
|
118
120
|
*
|
|
119
121
|
* @category models
|
|
120
122
|
* @since 3.18.0
|
|
121
123
|
*/
|
|
122
124
|
export interface Proto<out N, out E> extends Iterable<readonly [NodeIndex, N]>, Equal.Equal, Pipeable, Inspectable {
|
|
123
|
-
readonly [TypeId]:
|
|
124
|
-
readonly nodes: Map<NodeIndex, N>
|
|
125
|
-
readonly edges: Map<EdgeIndex, Edge<E>>
|
|
126
|
-
readonly adjacency: Map<NodeIndex, Array<EdgeIndex>>
|
|
127
|
-
readonly reverseAdjacency: Map<NodeIndex, Array<EdgeIndex>>
|
|
128
|
-
nextNodeIndex: NodeIndex
|
|
129
|
-
nextEdgeIndex: EdgeIndex
|
|
130
|
-
acyclic: Option.Option<boolean>
|
|
125
|
+
readonly [TypeId]: Graph.Variance<N, E>
|
|
131
126
|
}
|
|
132
127
|
|
|
133
128
|
/**
|
|
@@ -150,6 +145,25 @@ export interface Graph<out N, out E, T extends Kind = "directed"> extends Proto<
|
|
|
150
145
|
readonly mutable: false
|
|
151
146
|
}
|
|
152
147
|
|
|
148
|
+
/**
|
|
149
|
+
* Companion namespace containing type-level metadata for immutable graphs.
|
|
150
|
+
*
|
|
151
|
+
* @category models
|
|
152
|
+
* @since 4.0.0
|
|
153
|
+
*/
|
|
154
|
+
export declare namespace Graph {
|
|
155
|
+
/**
|
|
156
|
+
* Type-level variance marker for immutable graphs.
|
|
157
|
+
*
|
|
158
|
+
* @category models
|
|
159
|
+
* @since 4.0.0
|
|
160
|
+
*/
|
|
161
|
+
export interface Variance<out N, out E> {
|
|
162
|
+
readonly _N: Covariant<N>
|
|
163
|
+
readonly _E: Covariant<E>
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
153
167
|
/**
|
|
154
168
|
* Mutable graph interface.
|
|
155
169
|
*
|
|
@@ -166,11 +180,63 @@ export interface Graph<out N, out E, T extends Kind = "directed"> extends Proto<
|
|
|
166
180
|
* @category models
|
|
167
181
|
* @since 3.18.0
|
|
168
182
|
*/
|
|
169
|
-
export interface MutableGraph<out N, out E, T extends Kind = "directed">
|
|
183
|
+
export interface MutableGraph<in out N, in out E, T extends Kind = "directed">
|
|
184
|
+
extends Iterable<readonly [NodeIndex, N]>, Equal.Equal, Pipeable, Inspectable
|
|
185
|
+
{
|
|
186
|
+
readonly [TypeId]: MutableGraph.Variance<N, E>
|
|
170
187
|
readonly type: T
|
|
171
188
|
readonly mutable: true
|
|
172
189
|
}
|
|
173
190
|
|
|
191
|
+
/**
|
|
192
|
+
* Companion namespace containing type-level metadata for scoped mutable graphs.
|
|
193
|
+
*
|
|
194
|
+
* @category models
|
|
195
|
+
* @since 4.0.0
|
|
196
|
+
*/
|
|
197
|
+
export declare namespace MutableGraph {
|
|
198
|
+
/**
|
|
199
|
+
* Type-level variance marker for scoped mutable graphs.
|
|
200
|
+
*
|
|
201
|
+
* @category models
|
|
202
|
+
* @since 4.0.0
|
|
203
|
+
*/
|
|
204
|
+
export interface Variance<in out N, in out E> {
|
|
205
|
+
readonly _N: Invariant<N>
|
|
206
|
+
readonly _E: Invariant<E>
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** @internal */
|
|
211
|
+
interface GraphImpl<in out N, in out E, T extends Kind = "directed">
|
|
212
|
+
extends Iterable<readonly [NodeIndex, N]>, Equal.Equal, Pipeable, Inspectable
|
|
213
|
+
{
|
|
214
|
+
readonly [TypeId]: unknown
|
|
215
|
+
readonly type: T
|
|
216
|
+
mutable: boolean
|
|
217
|
+
nodes: Map<NodeIndex, N>
|
|
218
|
+
edges: Map<EdgeIndex, Edge<E>>
|
|
219
|
+
adjacency: Map<NodeIndex, Array<EdgeIndex>>
|
|
220
|
+
reverseAdjacency: Map<NodeIndex, Array<EdgeIndex>>
|
|
221
|
+
nextNodeIndex: NodeIndex
|
|
222
|
+
nextEdgeIndex: EdgeIndex
|
|
223
|
+
acyclic: Option.Option<boolean>
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/** @internal */
|
|
227
|
+
const graphImpl = <N, E, T extends Kind = "directed">(
|
|
228
|
+
graph: Graph<N, E, T> | MutableGraph<N, E, T>
|
|
229
|
+
): GraphImpl<N, E, T> => graph as unknown as GraphImpl<N, E, T>
|
|
230
|
+
|
|
231
|
+
/** @internal */
|
|
232
|
+
const cloneAdjacency = (adjacency: Map<NodeIndex, Array<EdgeIndex>>): Map<NodeIndex, Array<EdgeIndex>> => {
|
|
233
|
+
const cloned = new Map<NodeIndex, Array<EdgeIndex>>()
|
|
234
|
+
for (const [nodeIndex, edges] of adjacency) {
|
|
235
|
+
cloned.set(nodeIndex, [...edges])
|
|
236
|
+
}
|
|
237
|
+
return cloned
|
|
238
|
+
}
|
|
239
|
+
|
|
174
240
|
/**
|
|
175
241
|
* Immutable graph type for source-to-target relationships.
|
|
176
242
|
*
|
|
@@ -253,41 +319,61 @@ export type MutableUndirectedGraph<N, E> = MutableGraph<N, E, "undirected">
|
|
|
253
319
|
// Proto Objects
|
|
254
320
|
// =============================================================================
|
|
255
321
|
|
|
322
|
+
/** @internal */
|
|
323
|
+
const edgeEquals = (type: Kind, self: Edge<any>, that: Edge<any>): boolean =>
|
|
324
|
+
(type === "directed"
|
|
325
|
+
? self.source === that.source && self.target === that.target
|
|
326
|
+
: (self.source === that.source && self.target === that.target) ||
|
|
327
|
+
(self.source === that.target && self.target === that.source)) &&
|
|
328
|
+
Equal.equals(self.data, that.data)
|
|
329
|
+
|
|
330
|
+
/** @internal */
|
|
331
|
+
const edgeHash = (type: Kind, edge: Edge<any>): number =>
|
|
332
|
+
type === "directed"
|
|
333
|
+
? Hash.hash(edge)
|
|
334
|
+
: Hash.optimize(Hash.hash(edge.data) ^ (Hash.hash(edge.source) + Hash.hash(edge.target)))
|
|
335
|
+
|
|
256
336
|
/** @internal */
|
|
257
337
|
const ProtoGraph = {
|
|
258
|
-
[TypeId]:
|
|
259
|
-
|
|
338
|
+
[TypeId]: {
|
|
339
|
+
_N: (_: never) => _,
|
|
340
|
+
_E: (_: never) => _
|
|
341
|
+
},
|
|
342
|
+
[Symbol.iterator](this: GraphImpl<any, any>) {
|
|
260
343
|
return this.nodes[Symbol.iterator]()
|
|
261
344
|
},
|
|
262
|
-
[NodeInspectSymbol](this:
|
|
345
|
+
[NodeInspectSymbol](this: GraphImpl<any, any>) {
|
|
263
346
|
return this.toJSON()
|
|
264
347
|
},
|
|
265
|
-
[Equal.symbol](this:
|
|
348
|
+
[Equal.symbol](this: GraphImpl<any, any>, that: Equal.Equal): boolean {
|
|
266
349
|
if (isGraph(that)) {
|
|
350
|
+
const thatImpl = graphImpl(that)
|
|
267
351
|
if (
|
|
268
|
-
this.nodes.size !==
|
|
269
|
-
this.edges.size !==
|
|
270
|
-
this.
|
|
352
|
+
this.nodes.size !== thatImpl.nodes.size ||
|
|
353
|
+
this.edges.size !== thatImpl.edges.size ||
|
|
354
|
+
this.nextNodeIndex !== thatImpl.nextNodeIndex ||
|
|
355
|
+
this.nextEdgeIndex !== thatImpl.nextEdgeIndex ||
|
|
356
|
+
this.type !== thatImpl.type
|
|
271
357
|
) {
|
|
272
358
|
return false
|
|
273
359
|
}
|
|
274
360
|
// Compare nodes
|
|
275
361
|
for (const [nodeIndex, nodeData] of this.nodes) {
|
|
276
|
-
if (!
|
|
362
|
+
if (!thatImpl.nodes.has(nodeIndex)) {
|
|
277
363
|
return false
|
|
278
364
|
}
|
|
279
|
-
const otherNodeData =
|
|
365
|
+
const otherNodeData = thatImpl.nodes.get(nodeIndex)!
|
|
280
366
|
if (!Equal.equals(nodeData, otherNodeData)) {
|
|
281
367
|
return false
|
|
282
368
|
}
|
|
283
369
|
}
|
|
284
370
|
// Compare edges
|
|
285
371
|
for (const [edgeIndex, edgeData] of this.edges) {
|
|
286
|
-
if (!
|
|
372
|
+
if (!thatImpl.edges.has(edgeIndex)) {
|
|
287
373
|
return false
|
|
288
374
|
}
|
|
289
|
-
const otherEdge =
|
|
290
|
-
if (!
|
|
375
|
+
const otherEdge = thatImpl.edges.get(edgeIndex)!
|
|
376
|
+
if (!edgeEquals(this.type, edgeData, otherEdge)) {
|
|
291
377
|
return false
|
|
292
378
|
}
|
|
293
379
|
}
|
|
@@ -295,20 +381,22 @@ const ProtoGraph = {
|
|
|
295
381
|
}
|
|
296
382
|
return false
|
|
297
383
|
},
|
|
298
|
-
[Hash.symbol](this:
|
|
384
|
+
[Hash.symbol](this: GraphImpl<any, any>): number {
|
|
299
385
|
let hash = Hash.string("Graph")
|
|
300
386
|
hash = hash ^ Hash.string(this.type)
|
|
301
387
|
hash = hash ^ Hash.number(this.nodes.size)
|
|
302
388
|
hash = hash ^ Hash.number(this.edges.size)
|
|
389
|
+
hash = hash ^ Hash.number(this.nextNodeIndex)
|
|
390
|
+
hash = hash ^ Hash.number(this.nextEdgeIndex)
|
|
303
391
|
for (const [nodeIndex, nodeData] of this.nodes) {
|
|
304
392
|
hash = hash ^ (Hash.hash(nodeIndex) + Hash.hash(nodeData))
|
|
305
393
|
}
|
|
306
394
|
for (const [edgeIndex, edgeData] of this.edges) {
|
|
307
|
-
hash = hash ^ (Hash.hash(edgeIndex) +
|
|
395
|
+
hash = hash ^ (Hash.hash(edgeIndex) + edgeHash(this.type, edgeData))
|
|
308
396
|
}
|
|
309
397
|
return hash
|
|
310
398
|
},
|
|
311
|
-
toJSON(this:
|
|
399
|
+
toJSON(this: GraphImpl<any, any>) {
|
|
312
400
|
return {
|
|
313
401
|
_id: "Graph",
|
|
314
402
|
nodeCount: this.nodes.size,
|
|
@@ -316,7 +404,7 @@ const ProtoGraph = {
|
|
|
316
404
|
type: this.type
|
|
317
405
|
}
|
|
318
406
|
},
|
|
319
|
-
toString(this:
|
|
407
|
+
toString(this: GraphImpl<any, any>) {
|
|
320
408
|
return `Graph(${this.type}, ${this.nodes.size}, ${this.edges.size})`
|
|
321
409
|
},
|
|
322
410
|
pipe() {
|
|
@@ -350,13 +438,22 @@ export class GraphError extends Data.TaggedError("GraphError")<{
|
|
|
350
438
|
/** @internal */
|
|
351
439
|
const missingNode = (node: number) => new GraphError({ message: `Node ${node} does not exist` })
|
|
352
440
|
|
|
441
|
+
/** @internal */
|
|
442
|
+
function assertMutable<N, E, T extends Kind = "directed">(
|
|
443
|
+
graph: Graph<N, E, T> | MutableGraph<N, E, T>
|
|
444
|
+
): asserts graph is MutableGraph<N, E, T> {
|
|
445
|
+
if (!graph.mutable) {
|
|
446
|
+
throw new GraphError({ message: "Graph is not mutable" })
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
353
450
|
// =============================================================================
|
|
354
451
|
// Constructors
|
|
355
452
|
// =============================================================================
|
|
356
453
|
|
|
357
454
|
/**
|
|
358
455
|
* Returns `true` if a value has the graph runtime type identifier, narrowing
|
|
359
|
-
* it to
|
|
456
|
+
* it to an immutable or mutable graph.
|
|
360
457
|
*
|
|
361
458
|
* **When to use**
|
|
362
459
|
*
|
|
@@ -365,12 +462,63 @@ const missingNode = (node: number) => new GraphError({ message: `Node ${node} do
|
|
|
365
462
|
* **Gotchas**
|
|
366
463
|
*
|
|
367
464
|
* This guard checks the shared graph runtime type identifier and does not
|
|
368
|
-
* distinguish immutable graphs from mutable graphs
|
|
465
|
+
* distinguish immutable graphs from mutable graphs or directed graphs from
|
|
466
|
+
* undirected graphs.
|
|
369
467
|
*
|
|
370
468
|
* @category guards
|
|
371
469
|
* @since 4.0.0
|
|
372
470
|
*/
|
|
373
|
-
export const isGraph =
|
|
471
|
+
export const isGraph = <N = unknown, E = unknown, T extends Kind = Kind, U = never>(
|
|
472
|
+
u: U | Graph<N, E, T> | MutableGraph<N, E, T>
|
|
473
|
+
): u is Graph<N, E, T> | MutableGraph<N, E, T> => hasProperty(u, TypeId)
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Creates a graph constructor for the specified graph kind.
|
|
477
|
+
*
|
|
478
|
+
* **When to use**
|
|
479
|
+
*
|
|
480
|
+
* Use when the graph kind is selected dynamically. Prefer `directed` or
|
|
481
|
+
* `undirected` when the kind is known statically.
|
|
482
|
+
*
|
|
483
|
+
* **Example** (Constructing by kind)
|
|
484
|
+
*
|
|
485
|
+
* ```ts
|
|
486
|
+
* import { Graph } from "effect"
|
|
487
|
+
*
|
|
488
|
+
* const makeGraph = Graph.make("directed")
|
|
489
|
+
* const graph = makeGraph<string, number>((mutable) => {
|
|
490
|
+
* Graph.addNode(mutable, "A")
|
|
491
|
+
* })
|
|
492
|
+
*
|
|
493
|
+
* console.log(graph.type) // "directed"
|
|
494
|
+
* ```
|
|
495
|
+
*
|
|
496
|
+
* @see {@link directed} for constructing a directed graph directly
|
|
497
|
+
* @see {@link undirected} for constructing an undirected graph directly
|
|
498
|
+
* @category constructors
|
|
499
|
+
* @since 4.0.0
|
|
500
|
+
*/
|
|
501
|
+
export const make =
|
|
502
|
+
<T extends Kind>(type: T) => <N, E>(mutate?: (mutable: MutableGraph<N, E, T>) => undefined): Graph<N, E, T> => {
|
|
503
|
+
const graph: Mutable<GraphImpl<N, E, T>> = Object.create(ProtoGraph)
|
|
504
|
+
graph.type = type
|
|
505
|
+
graph.nodes = new Map()
|
|
506
|
+
graph.edges = new Map()
|
|
507
|
+
graph.adjacency = new Map()
|
|
508
|
+
graph.reverseAdjacency = new Map()
|
|
509
|
+
graph.nextNodeIndex = 0
|
|
510
|
+
graph.nextEdgeIndex = 0
|
|
511
|
+
graph.acyclic = Option.some(true)
|
|
512
|
+
|
|
513
|
+
if (mutate === undefined) {
|
|
514
|
+
graph.mutable = false
|
|
515
|
+
return graph as unknown as Graph<N, E, T>
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
graph.mutable = true
|
|
519
|
+
const mutable = Equal.byReferenceUnsafe(graph as unknown as MutableGraph<N, E, T>)
|
|
520
|
+
return mutateScoped(mutable, mutate)
|
|
521
|
+
}
|
|
374
522
|
|
|
375
523
|
/**
|
|
376
524
|
* Creates a directed graph, optionally with initial mutations.
|
|
@@ -393,26 +541,9 @@ export const isGraph = (u: unknown): u is Graph<unknown, unknown> => hasProperty
|
|
|
393
541
|
* @category constructors
|
|
394
542
|
* @since 3.18.0
|
|
395
543
|
*/
|
|
396
|
-
export const directed
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
graph.nodes = new Map()
|
|
400
|
-
graph.edges = new Map()
|
|
401
|
-
graph.adjacency = new Map()
|
|
402
|
-
graph.reverseAdjacency = new Map()
|
|
403
|
-
graph.nextNodeIndex = 0
|
|
404
|
-
graph.nextEdgeIndex = 0
|
|
405
|
-
graph.acyclic = Option.some(true)
|
|
406
|
-
graph.mutable = false
|
|
407
|
-
|
|
408
|
-
if (mutate) {
|
|
409
|
-
const mutable = beginMutation(graph as DirectedGraph<N, E>)
|
|
410
|
-
mutate(mutable as MutableDirectedGraph<N, E>)
|
|
411
|
-
return endMutation(mutable)
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
return graph
|
|
415
|
-
}
|
|
544
|
+
export const directed: <N, E>(
|
|
545
|
+
mutate?: (mutable: MutableDirectedGraph<N, E>) => undefined
|
|
546
|
+
) => DirectedGraph<N, E> = make("directed")
|
|
416
547
|
|
|
417
548
|
/**
|
|
418
549
|
* Creates an undirected graph, optionally with initial mutations.
|
|
@@ -435,26 +566,9 @@ export const directed = <N, E>(mutate?: (mutable: MutableDirectedGraph<N, E>) =>
|
|
|
435
566
|
* @category constructors
|
|
436
567
|
* @since 3.18.0
|
|
437
568
|
*/
|
|
438
|
-
export const undirected
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
graph.nodes = new Map()
|
|
442
|
-
graph.edges = new Map()
|
|
443
|
-
graph.adjacency = new Map()
|
|
444
|
-
graph.reverseAdjacency = new Map()
|
|
445
|
-
graph.nextNodeIndex = 0
|
|
446
|
-
graph.nextEdgeIndex = 0
|
|
447
|
-
graph.acyclic = Option.some(true)
|
|
448
|
-
graph.mutable = false
|
|
449
|
-
|
|
450
|
-
if (mutate) {
|
|
451
|
-
const mutable = beginMutation(graph)
|
|
452
|
-
mutate(mutable as MutableUndirectedGraph<N, E>)
|
|
453
|
-
return endMutation(mutable)
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
return graph
|
|
457
|
-
}
|
|
569
|
+
export const undirected: <N, E>(
|
|
570
|
+
mutate?: (mutable: MutableUndirectedGraph<N, E>) => undefined
|
|
571
|
+
) => UndirectedGraph<N, E> = make("undirected")
|
|
458
572
|
|
|
459
573
|
// =============================================================================
|
|
460
574
|
// Scoped Mutable API
|
|
@@ -479,35 +593,32 @@ export const undirected = <N, E>(mutate?: (mutable: MutableUndirectedGraph<N, E>
|
|
|
479
593
|
export const beginMutation = <N, E, T extends Kind = "directed">(
|
|
480
594
|
graph: Graph<N, E, T>
|
|
481
595
|
): MutableGraph<N, E, T> => {
|
|
482
|
-
|
|
483
|
-
const adjacency =
|
|
484
|
-
const reverseAdjacency =
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
for (const [nodeIndex, edges] of graph.reverseAdjacency) {
|
|
491
|
-
reverseAdjacency.set(nodeIndex, [...edges])
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
const mutable: Mutable<MutableGraph<N, E, T>> = Object.create(ProtoGraph)
|
|
495
|
-
mutable.type = graph.type
|
|
496
|
-
mutable.nodes = new Map(graph.nodes)
|
|
497
|
-
mutable.edges = new Map(graph.edges)
|
|
596
|
+
const source = graphImpl(graph)
|
|
597
|
+
const adjacency = cloneAdjacency(source.adjacency)
|
|
598
|
+
const reverseAdjacency = cloneAdjacency(source.reverseAdjacency)
|
|
599
|
+
|
|
600
|
+
const mutable: Mutable<GraphImpl<N, E, T>> = Object.create(ProtoGraph)
|
|
601
|
+
mutable.type = source.type
|
|
602
|
+
mutable.nodes = new Map(source.nodes)
|
|
603
|
+
mutable.edges = new Map(source.edges)
|
|
498
604
|
mutable.adjacency = adjacency
|
|
499
605
|
mutable.reverseAdjacency = reverseAdjacency
|
|
500
|
-
mutable.nextNodeIndex =
|
|
501
|
-
mutable.nextEdgeIndex =
|
|
502
|
-
mutable.acyclic =
|
|
606
|
+
mutable.nextNodeIndex = source.nextNodeIndex
|
|
607
|
+
mutable.nextEdgeIndex = source.nextEdgeIndex
|
|
608
|
+
mutable.acyclic = source.acyclic
|
|
503
609
|
mutable.mutable = true
|
|
504
610
|
|
|
505
|
-
return mutable
|
|
611
|
+
return Equal.byReferenceUnsafe(mutable as unknown as MutableGraph<N, E, T>)
|
|
506
612
|
}
|
|
507
613
|
|
|
508
614
|
/**
|
|
509
615
|
* Converts a mutable graph back to an immutable graph, ending the mutation scope.
|
|
510
616
|
*
|
|
617
|
+
* **Details**
|
|
618
|
+
*
|
|
619
|
+
* Finalizes the mutable handle. Later public mutation operations on that handle
|
|
620
|
+
* fail with a `GraphError`.
|
|
621
|
+
*
|
|
511
622
|
* **Example** (Ending a mutation scope)
|
|
512
623
|
*
|
|
513
624
|
* ```ts
|
|
@@ -525,96 +636,2247 @@ export const beginMutation = <N, E, T extends Kind = "directed">(
|
|
|
525
636
|
export const endMutation = <N, E, T extends Kind = "directed">(
|
|
526
637
|
mutable: MutableGraph<N, E, T>
|
|
527
638
|
): Graph<N, E, T> => {
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
graph
|
|
532
|
-
graph.
|
|
533
|
-
graph.
|
|
534
|
-
graph.
|
|
535
|
-
graph.
|
|
536
|
-
graph.
|
|
639
|
+
assertMutable(mutable)
|
|
640
|
+
const source = graphImpl(mutable)
|
|
641
|
+
|
|
642
|
+
const graph: Mutable<GraphImpl<N, E, T>> = Object.create(ProtoGraph)
|
|
643
|
+
graph.type = source.type
|
|
644
|
+
graph.nodes = new Map(source.nodes)
|
|
645
|
+
graph.edges = new Map(source.edges)
|
|
646
|
+
graph.adjacency = cloneAdjacency(source.adjacency)
|
|
647
|
+
graph.reverseAdjacency = cloneAdjacency(source.reverseAdjacency)
|
|
648
|
+
graph.nextNodeIndex = source.nextNodeIndex
|
|
649
|
+
graph.nextEdgeIndex = source.nextEdgeIndex
|
|
650
|
+
graph.acyclic = source.acyclic
|
|
537
651
|
graph.mutable = false
|
|
652
|
+
source.mutable = false
|
|
653
|
+
|
|
654
|
+
return graph as unknown as Graph<N, E, T>
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
/** @internal */
|
|
658
|
+
const mutateScoped = <N, E, T extends Kind>(
|
|
659
|
+
mutable: MutableGraph<N, E, T>,
|
|
660
|
+
f: (mutable: MutableGraph<N, E, T>) => undefined
|
|
661
|
+
): Graph<N, E, T> => {
|
|
662
|
+
let graph: Graph<N, E, T>
|
|
663
|
+
try {
|
|
664
|
+
f(mutable)
|
|
665
|
+
} finally {
|
|
666
|
+
graph = endMutation(mutable)
|
|
667
|
+
}
|
|
668
|
+
return graph
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
/**
|
|
672
|
+
* Performs scoped mutations on a graph, automatically managing the mutation lifecycle.
|
|
673
|
+
*
|
|
674
|
+
* **Example** (Applying scoped mutations)
|
|
675
|
+
*
|
|
676
|
+
* ```ts
|
|
677
|
+
* import { Graph } from "effect"
|
|
678
|
+
*
|
|
679
|
+
* const graph = Graph.directed<string, number>()
|
|
680
|
+
* const newGraph = Graph.mutate(graph, (mutable) => {
|
|
681
|
+
* const nodeA = Graph.addNode(mutable, "A")
|
|
682
|
+
* const nodeB = Graph.addNode(mutable, "B")
|
|
683
|
+
* Graph.addEdge(mutable, nodeA, nodeB, 1)
|
|
684
|
+
* })
|
|
685
|
+
*
|
|
686
|
+
* console.log(Graph.nodeCount(newGraph)) // 2
|
|
687
|
+
* console.log(Graph.edgeCount(newGraph)) // 1
|
|
688
|
+
* ```
|
|
689
|
+
*
|
|
690
|
+
* @category mutations
|
|
691
|
+
* @since 3.18.0
|
|
692
|
+
*/
|
|
693
|
+
export const mutate: {
|
|
694
|
+
/**
|
|
695
|
+
* Performs scoped mutations on a graph, automatically managing the mutation lifecycle.
|
|
696
|
+
*
|
|
697
|
+
* **Example** (Applying scoped mutations)
|
|
698
|
+
*
|
|
699
|
+
* ```ts
|
|
700
|
+
* import { Graph } from "effect"
|
|
701
|
+
*
|
|
702
|
+
* const graph = Graph.directed<string, number>()
|
|
703
|
+
* const newGraph = Graph.mutate(graph, (mutable) => {
|
|
704
|
+
* const nodeA = Graph.addNode(mutable, "A")
|
|
705
|
+
* const nodeB = Graph.addNode(mutable, "B")
|
|
706
|
+
* Graph.addEdge(mutable, nodeA, nodeB, 1)
|
|
707
|
+
* })
|
|
708
|
+
*
|
|
709
|
+
* console.log(Graph.nodeCount(newGraph)) // 2
|
|
710
|
+
* console.log(Graph.edgeCount(newGraph)) // 1
|
|
711
|
+
* ```
|
|
712
|
+
*
|
|
713
|
+
* @category mutations
|
|
714
|
+
* @since 3.18.0
|
|
715
|
+
*/
|
|
716
|
+
<N, E, T extends Kind = "directed">(f: (mutable: MutableGraph<N, E, T>) => undefined): (graph: Graph<N, E, T>) => Graph<N, E, T>
|
|
717
|
+
/**
|
|
718
|
+
* Performs scoped mutations on a graph, automatically managing the mutation lifecycle.
|
|
719
|
+
*
|
|
720
|
+
* **Example** (Applying scoped mutations)
|
|
721
|
+
*
|
|
722
|
+
* ```ts
|
|
723
|
+
* import { Graph } from "effect"
|
|
724
|
+
*
|
|
725
|
+
* const graph = Graph.directed<string, number>()
|
|
726
|
+
* const newGraph = Graph.mutate(graph, (mutable) => {
|
|
727
|
+
* const nodeA = Graph.addNode(mutable, "A")
|
|
728
|
+
* const nodeB = Graph.addNode(mutable, "B")
|
|
729
|
+
* Graph.addEdge(mutable, nodeA, nodeB, 1)
|
|
730
|
+
* })
|
|
731
|
+
*
|
|
732
|
+
* console.log(Graph.nodeCount(newGraph)) // 2
|
|
733
|
+
* console.log(Graph.edgeCount(newGraph)) // 1
|
|
734
|
+
* ```
|
|
735
|
+
*
|
|
736
|
+
* @category mutations
|
|
737
|
+
* @since 3.18.0
|
|
738
|
+
*/
|
|
739
|
+
<N, E, T extends Kind = "directed">(graph: Graph<N, E, T>, f: (mutable: MutableGraph<N, E, T>) => undefined): Graph<N, E, T>
|
|
740
|
+
} = dual(2, <N, E, T extends Kind = "directed">(
|
|
741
|
+
graph: Graph<N, E, T>,
|
|
742
|
+
f: (mutable: MutableGraph<N, E, T>) => undefined
|
|
743
|
+
): Graph<N, E, T> => {
|
|
744
|
+
const mutable = beginMutation(graph)
|
|
745
|
+
return mutateScoped(mutable, f)
|
|
746
|
+
})
|
|
747
|
+
|
|
748
|
+
// =============================================================================
|
|
749
|
+
// Set Operations
|
|
750
|
+
// =============================================================================
|
|
751
|
+
|
|
752
|
+
/** @internal */
|
|
753
|
+
type NodeMaps<N, I> = {
|
|
754
|
+
readonly byIdentity: MutableHashMap.MutableHashMap<I, N>
|
|
755
|
+
readonly byIndex: Map<NodeIndex, I>
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
/** @internal */
|
|
759
|
+
class EdgeIdentity<NI, EI> implements Equal.Equal {
|
|
760
|
+
readonly type: Kind
|
|
761
|
+
readonly source: NI
|
|
762
|
+
readonly target: NI
|
|
763
|
+
readonly identity: EI
|
|
764
|
+
|
|
765
|
+
constructor(
|
|
766
|
+
type: Kind,
|
|
767
|
+
source: NI,
|
|
768
|
+
target: NI,
|
|
769
|
+
identity: EI
|
|
770
|
+
) {
|
|
771
|
+
this.type = type
|
|
772
|
+
this.source = source
|
|
773
|
+
this.target = target
|
|
774
|
+
this.identity = identity
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
[Equal.symbol](that: Equal.Equal): boolean {
|
|
778
|
+
if (!(that instanceof EdgeIdentity) || this.type !== that.type || !Equal.equals(this.identity, that.identity)) {
|
|
779
|
+
return false
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
if (this.type === "directed") {
|
|
783
|
+
return Equal.equals(this.source, that.source) && Equal.equals(this.target, that.target)
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
return (
|
|
787
|
+
(Equal.equals(this.source, that.source) && Equal.equals(this.target, that.target)) ||
|
|
788
|
+
(Equal.equals(this.source, that.target) && Equal.equals(this.target, that.source))
|
|
789
|
+
)
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
[Hash.symbol](): number {
|
|
793
|
+
const hash = Hash.hash(this.identity)
|
|
794
|
+
return this.type === "directed"
|
|
795
|
+
? Hash.combine(Hash.hash(this.target))(Hash.combine(Hash.hash(this.source))(hash))
|
|
796
|
+
: Hash.optimize(hash ^ (Hash.hash(this.source) + Hash.hash(this.target)))
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
/**
|
|
801
|
+
* Configures node and edge identity for graph set operations.
|
|
802
|
+
*
|
|
803
|
+
* **Details**
|
|
804
|
+
*
|
|
805
|
+
* Both functions default to using the complete node or edge data. Edge identity
|
|
806
|
+
* also includes the identities of its endpoint nodes and the graph kind.
|
|
807
|
+
*
|
|
808
|
+
* **Gotchas**
|
|
809
|
+
*
|
|
810
|
+
* Edge identity defines set membership, not edge multiplicity. Parallel edges
|
|
811
|
+
* with the same endpoint identities and projected edge identity are treated as
|
|
812
|
+
* the same member by graph set operations.
|
|
813
|
+
*
|
|
814
|
+
* @category models
|
|
815
|
+
* @since 4.0.0
|
|
816
|
+
*/
|
|
817
|
+
export interface IdentityOptions<N, E, NI = N, EI = E> {
|
|
818
|
+
readonly nodeIdentity?: (node: N) => NI
|
|
819
|
+
readonly edgeIdentity?: (edge: E) => EI
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
/** @internal */
|
|
823
|
+
const buildNodeMaps = <N, E, T extends Kind, I>(
|
|
824
|
+
graph: Graph<N, E, T>,
|
|
825
|
+
identity: (node: N) => I
|
|
826
|
+
): NodeMaps<N, I> => {
|
|
827
|
+
const impl = graphImpl(graph)
|
|
828
|
+
const byIdentity = MutableHashMap.empty<I, N>()
|
|
829
|
+
const byIndex = new Map<NodeIndex, I>()
|
|
830
|
+
|
|
831
|
+
for (const [index, data] of impl.nodes) {
|
|
832
|
+
const nodeIdentity = identity(data)
|
|
833
|
+
MutableHashMap.set(byIdentity, nodeIdentity, data)
|
|
834
|
+
byIndex.set(index, nodeIdentity)
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
return { byIdentity, byIndex }
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
/** @internal */
|
|
841
|
+
const nodeIdentityAt = <N, I>(maps: NodeMaps<N, I>, index: NodeIndex): I => maps.byIndex.get(index) as I
|
|
842
|
+
|
|
843
|
+
/** @internal */
|
|
844
|
+
const buildEdgeMap = <N, E, T extends Kind, NI, EI>(
|
|
845
|
+
graph: Graph<N, E, T>,
|
|
846
|
+
nodeMaps: NodeMaps<N, NI>,
|
|
847
|
+
identity: (edge: E) => EI
|
|
848
|
+
): MutableHashMap.MutableHashMap<EdgeIdentity<NI, EI>, E> => {
|
|
849
|
+
const impl = graphImpl(graph)
|
|
850
|
+
const edges = MutableHashMap.empty<EdgeIdentity<NI, EI>, E>()
|
|
851
|
+
for (const edge of impl.edges.values()) {
|
|
852
|
+
const sourceIdentity = nodeIdentityAt(nodeMaps, edge.source)
|
|
853
|
+
const targetIdentity = nodeIdentityAt(nodeMaps, edge.target)
|
|
854
|
+
MutableHashMap.set(
|
|
855
|
+
edges,
|
|
856
|
+
new EdgeIdentity(graph.type, sourceIdentity, targetIdentity, identity(edge.data)),
|
|
857
|
+
edge.data
|
|
858
|
+
)
|
|
859
|
+
}
|
|
860
|
+
return edges
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
/** @internal */
|
|
864
|
+
const addNodesByIdentity = <N, E, T extends Kind, NI>(
|
|
865
|
+
mutable: MutableGraph<N, E, T>,
|
|
866
|
+
nodes: Iterable<readonly [NI, N]>
|
|
867
|
+
): MutableHashMap.MutableHashMap<NI, NodeIndex> => {
|
|
868
|
+
const indexByIdentity = MutableHashMap.empty<NI, NodeIndex>()
|
|
869
|
+
for (const [identity, data] of nodes) {
|
|
870
|
+
MutableHashMap.set(indexByIdentity, identity, addNode(mutable, data))
|
|
871
|
+
}
|
|
872
|
+
return indexByIdentity
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
/** @internal */
|
|
876
|
+
const addEdgeByIdentity = <N, E, T extends Kind, NI, EI>(
|
|
877
|
+
mutable: MutableGraph<N, E, T>,
|
|
878
|
+
indexByIdentity: MutableHashMap.MutableHashMap<NI, NodeIndex>,
|
|
879
|
+
identity: EdgeIdentity<NI, EI>,
|
|
880
|
+
data: E
|
|
881
|
+
): void => {
|
|
882
|
+
const sourceIndex = Option.getOrUndefined(MutableHashMap.get(indexByIdentity, identity.source))
|
|
883
|
+
const targetIndex = Option.getOrUndefined(MutableHashMap.get(indexByIdentity, identity.target))
|
|
884
|
+
if (sourceIndex !== undefined && targetIndex !== undefined) {
|
|
885
|
+
addEdge(mutable, sourceIndex, targetIndex, data)
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
/** @internal */
|
|
890
|
+
const assertSameKind = <N, E>(self: Graph<N, E, Kind>, that: Graph<N, E, Kind>): void => {
|
|
891
|
+
if (self.type !== that.type) {
|
|
892
|
+
throw new GraphError({ message: `Cannot combine ${self.type} and ${that.type} graphs` })
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
/**
|
|
897
|
+
* Composes two graphs, merging nodes by identity.
|
|
898
|
+
*
|
|
899
|
+
* **Details**
|
|
900
|
+
*
|
|
901
|
+
* Nodes and edges present in both graphs use data from `that`. The result has
|
|
902
|
+
* the same graph kind as `self`. Throws a `GraphError` when the graph kinds do
|
|
903
|
+
* not match. `nodeIdentity` and `edgeIdentity` default to the complete node and
|
|
904
|
+
* edge data. Edge identity also includes the endpoint identities.
|
|
905
|
+
*
|
|
906
|
+
* `G1 ∪ G2 = {V1 ∪ V2, E1 ∪ E2}`
|
|
907
|
+
*
|
|
908
|
+
* **Gotchas**
|
|
909
|
+
*
|
|
910
|
+
* Nodes with equal identities in one input graph are coalesced. The last node
|
|
911
|
+
* supplies the data, and redirected edges can collapse or become self-loops.
|
|
912
|
+
* Parallel edges with equal identities are also coalesced, with the last edge
|
|
913
|
+
* supplying the data.
|
|
914
|
+
*
|
|
915
|
+
* **Example** (Combining graphs)
|
|
916
|
+
*
|
|
917
|
+
* ```ts
|
|
918
|
+
* import { Graph } from "effect"
|
|
919
|
+
*
|
|
920
|
+
* const left = Graph.directed<{ id: string }, string>((mutable) => {
|
|
921
|
+
* const a = Graph.addNode(mutable, { id: "A" })
|
|
922
|
+
* const b = Graph.addNode(mutable, { id: "B" })
|
|
923
|
+
* Graph.addEdge(mutable, a, b, "A-B")
|
|
924
|
+
* })
|
|
925
|
+
*
|
|
926
|
+
* const right = Graph.directed<{ id: string }, string>((mutable) => {
|
|
927
|
+
* const b = Graph.addNode(mutable, { id: "B" })
|
|
928
|
+
* const c = Graph.addNode(mutable, { id: "C" })
|
|
929
|
+
* Graph.addEdge(mutable, b, c, "B-C")
|
|
930
|
+
* })
|
|
931
|
+
*
|
|
932
|
+
* const result = Graph.compose(left, right, {
|
|
933
|
+
* nodeIdentity: (node) => node.id
|
|
934
|
+
* })
|
|
935
|
+
*
|
|
936
|
+
* console.log(Graph.nodeCount(result)) // 3
|
|
937
|
+
* console.log(Graph.edgeCount(result)) // 2
|
|
938
|
+
* ```
|
|
939
|
+
*
|
|
940
|
+
* @category set operations
|
|
941
|
+
* @since 4.0.0
|
|
942
|
+
*/
|
|
943
|
+
export const compose: {
|
|
944
|
+
/**
|
|
945
|
+
* Composes two graphs, merging nodes by identity.
|
|
946
|
+
*
|
|
947
|
+
* **Details**
|
|
948
|
+
*
|
|
949
|
+
* Nodes and edges present in both graphs use data from `that`. The result has
|
|
950
|
+
* the same graph kind as `self`. Throws a `GraphError` when the graph kinds do
|
|
951
|
+
* not match. `nodeIdentity` and `edgeIdentity` default to the complete node and
|
|
952
|
+
* edge data. Edge identity also includes the endpoint identities.
|
|
953
|
+
*
|
|
954
|
+
* `G1 ∪ G2 = {V1 ∪ V2, E1 ∪ E2}`
|
|
955
|
+
*
|
|
956
|
+
* **Gotchas**
|
|
957
|
+
*
|
|
958
|
+
* Nodes with equal identities in one input graph are coalesced. The last node
|
|
959
|
+
* supplies the data, and redirected edges can collapse or become self-loops.
|
|
960
|
+
* Parallel edges with equal identities are also coalesced, with the last edge
|
|
961
|
+
* supplying the data.
|
|
962
|
+
*
|
|
963
|
+
* **Example** (Combining graphs)
|
|
964
|
+
*
|
|
965
|
+
* ```ts
|
|
966
|
+
* import { Graph } from "effect"
|
|
967
|
+
*
|
|
968
|
+
* const left = Graph.directed<{ id: string }, string>((mutable) => {
|
|
969
|
+
* const a = Graph.addNode(mutable, { id: "A" })
|
|
970
|
+
* const b = Graph.addNode(mutable, { id: "B" })
|
|
971
|
+
* Graph.addEdge(mutable, a, b, "A-B")
|
|
972
|
+
* })
|
|
973
|
+
*
|
|
974
|
+
* const right = Graph.directed<{ id: string }, string>((mutable) => {
|
|
975
|
+
* const b = Graph.addNode(mutable, { id: "B" })
|
|
976
|
+
* const c = Graph.addNode(mutable, { id: "C" })
|
|
977
|
+
* Graph.addEdge(mutable, b, c, "B-C")
|
|
978
|
+
* })
|
|
979
|
+
*
|
|
980
|
+
* const result = Graph.compose(left, right, {
|
|
981
|
+
* nodeIdentity: (node) => node.id
|
|
982
|
+
* })
|
|
983
|
+
*
|
|
984
|
+
* console.log(Graph.nodeCount(result)) // 3
|
|
985
|
+
* console.log(Graph.edgeCount(result)) // 2
|
|
986
|
+
* ```
|
|
987
|
+
*
|
|
988
|
+
* @category set operations
|
|
989
|
+
* @since 4.0.0
|
|
990
|
+
*/
|
|
991
|
+
<N, E, T extends Kind = "directed", NI = N, EI = E>(that: Graph<N, E, T>, options?: IdentityOptions<N, E, NI, EI>): (self: Graph<N, E, NoInfer<T>>) => Graph<N, E, T>
|
|
992
|
+
/**
|
|
993
|
+
* Composes two graphs, merging nodes by identity.
|
|
994
|
+
*
|
|
995
|
+
* **Details**
|
|
996
|
+
*
|
|
997
|
+
* Nodes and edges present in both graphs use data from `that`. The result has
|
|
998
|
+
* the same graph kind as `self`. Throws a `GraphError` when the graph kinds do
|
|
999
|
+
* not match. `nodeIdentity` and `edgeIdentity` default to the complete node and
|
|
1000
|
+
* edge data. Edge identity also includes the endpoint identities.
|
|
1001
|
+
*
|
|
1002
|
+
* `G1 ∪ G2 = {V1 ∪ V2, E1 ∪ E2}`
|
|
1003
|
+
*
|
|
1004
|
+
* **Gotchas**
|
|
1005
|
+
*
|
|
1006
|
+
* Nodes with equal identities in one input graph are coalesced. The last node
|
|
1007
|
+
* supplies the data, and redirected edges can collapse or become self-loops.
|
|
1008
|
+
* Parallel edges with equal identities are also coalesced, with the last edge
|
|
1009
|
+
* supplying the data.
|
|
1010
|
+
*
|
|
1011
|
+
* **Example** (Combining graphs)
|
|
1012
|
+
*
|
|
1013
|
+
* ```ts
|
|
1014
|
+
* import { Graph } from "effect"
|
|
1015
|
+
*
|
|
1016
|
+
* const left = Graph.directed<{ id: string }, string>((mutable) => {
|
|
1017
|
+
* const a = Graph.addNode(mutable, { id: "A" })
|
|
1018
|
+
* const b = Graph.addNode(mutable, { id: "B" })
|
|
1019
|
+
* Graph.addEdge(mutable, a, b, "A-B")
|
|
1020
|
+
* })
|
|
1021
|
+
*
|
|
1022
|
+
* const right = Graph.directed<{ id: string }, string>((mutable) => {
|
|
1023
|
+
* const b = Graph.addNode(mutable, { id: "B" })
|
|
1024
|
+
* const c = Graph.addNode(mutable, { id: "C" })
|
|
1025
|
+
* Graph.addEdge(mutable, b, c, "B-C")
|
|
1026
|
+
* })
|
|
1027
|
+
*
|
|
1028
|
+
* const result = Graph.compose(left, right, {
|
|
1029
|
+
* nodeIdentity: (node) => node.id
|
|
1030
|
+
* })
|
|
1031
|
+
*
|
|
1032
|
+
* console.log(Graph.nodeCount(result)) // 3
|
|
1033
|
+
* console.log(Graph.edgeCount(result)) // 2
|
|
1034
|
+
* ```
|
|
1035
|
+
*
|
|
1036
|
+
* @category set operations
|
|
1037
|
+
* @since 4.0.0
|
|
1038
|
+
*/
|
|
1039
|
+
<N, E, T extends Kind = "directed", NI = N, EI = E>(
|
|
1040
|
+
self: Graph<N, E, T>,
|
|
1041
|
+
that: Graph<N, E, NoInfer<T>>,
|
|
1042
|
+
options?: IdentityOptions<N, E, NI, EI>
|
|
1043
|
+
): Graph<N, E, T>
|
|
1044
|
+
} = dual(
|
|
1045
|
+
(args) => isGraph(args[0]) && isGraph(args[1]),
|
|
1046
|
+
<N, E, T extends Kind, NI, EI>(
|
|
1047
|
+
self: Graph<N, E, T>,
|
|
1048
|
+
that: Graph<N, E, T>,
|
|
1049
|
+
options?: IdentityOptions<N, E, NI, EI>
|
|
1050
|
+
): Graph<N, E, T> => {
|
|
1051
|
+
assertSameKind(self, that)
|
|
1052
|
+
const getNodeIdentity = options?.nodeIdentity ?? ((node: N) => node as unknown as NI)
|
|
1053
|
+
const getEdgeIdentity = options?.edgeIdentity ?? ((edge: E) => edge as unknown as EI)
|
|
1054
|
+
const selfMaps = buildNodeMaps(self, getNodeIdentity)
|
|
1055
|
+
const thatMaps = buildNodeMaps(that, getNodeIdentity)
|
|
1056
|
+
const nodes = MutableHashMap.empty</**
|
|
1057
|
+
* Composes two graphs, merging nodes by identity.
|
|
1058
|
+
*
|
|
1059
|
+
* **Details**
|
|
1060
|
+
*
|
|
1061
|
+
* Nodes and edges present in both graphs use data from `that`. The result has
|
|
1062
|
+
* the same graph kind as `self`. Throws a `GraphError` when the graph kinds do
|
|
1063
|
+
* not match. `nodeIdentity` and `edgeIdentity` default to the complete node and
|
|
1064
|
+
* edge data. Edge identity also includes the endpoint identities.
|
|
1065
|
+
*
|
|
1066
|
+
* `G1 ∪ G2 = {V1 ∪ V2, E1 ∪ E2}`
|
|
1067
|
+
*
|
|
1068
|
+
* **Gotchas**
|
|
1069
|
+
*
|
|
1070
|
+
* Nodes with equal identities in one input graph are coalesced. The last node
|
|
1071
|
+
* supplies the data, and redirected edges can collapse or become self-loops.
|
|
1072
|
+
* Parallel edges with equal identities are also coalesced, with the last edge
|
|
1073
|
+
* supplying the data.
|
|
1074
|
+
*
|
|
1075
|
+
* **Example** (Combining graphs)
|
|
1076
|
+
*
|
|
1077
|
+
* ```ts
|
|
1078
|
+
* import { Graph } from "effect"
|
|
1079
|
+
*
|
|
1080
|
+
* const left = Graph.directed<{ id: string }, string>((mutable) => {
|
|
1081
|
+
* const a = Graph.addNode(mutable, { id: "A" })
|
|
1082
|
+
* const b = Graph.addNode(mutable, { id: "B" })
|
|
1083
|
+
* Graph.addEdge(mutable, a, b, "A-B")
|
|
1084
|
+
* })
|
|
1085
|
+
*
|
|
1086
|
+
* const right = Graph.directed<{ id: string }, string>((mutable) => {
|
|
1087
|
+
* const b = Graph.addNode(mutable, { id: "B" })
|
|
1088
|
+
* const c = Graph.addNode(mutable, { id: "C" })
|
|
1089
|
+
* Graph.addEdge(mutable, b, c, "B-C")
|
|
1090
|
+
* })
|
|
1091
|
+
*
|
|
1092
|
+
* const result = Graph.compose(left, right, {
|
|
1093
|
+
* nodeIdentity: (node) => node.id
|
|
1094
|
+
* })
|
|
1095
|
+
*
|
|
1096
|
+
* console.log(Graph.nodeCount(result)) // 3
|
|
1097
|
+
* console.log(Graph.edgeCount(result)) // 2
|
|
1098
|
+
* ```
|
|
1099
|
+
*
|
|
1100
|
+
* @category set operations
|
|
1101
|
+
* @since 4.0.0
|
|
1102
|
+
*/
|
|
1103
|
+
NI, /**
|
|
1104
|
+
* Composes two graphs, merging nodes by identity.
|
|
1105
|
+
*
|
|
1106
|
+
* **Details**
|
|
1107
|
+
*
|
|
1108
|
+
* Nodes and edges present in both graphs use data from `that`. The result has
|
|
1109
|
+
* the same graph kind as `self`. Throws a `GraphError` when the graph kinds do
|
|
1110
|
+
* not match. `nodeIdentity` and `edgeIdentity` default to the complete node and
|
|
1111
|
+
* edge data. Edge identity also includes the endpoint identities.
|
|
1112
|
+
*
|
|
1113
|
+
* `G1 ∪ G2 = {V1 ∪ V2, E1 ∪ E2}`
|
|
1114
|
+
*
|
|
1115
|
+
* **Gotchas**
|
|
1116
|
+
*
|
|
1117
|
+
* Nodes with equal identities in one input graph are coalesced. The last node
|
|
1118
|
+
* supplies the data, and redirected edges can collapse or become self-loops.
|
|
1119
|
+
* Parallel edges with equal identities are also coalesced, with the last edge
|
|
1120
|
+
* supplying the data.
|
|
1121
|
+
*
|
|
1122
|
+
* **Example** (Combining graphs)
|
|
1123
|
+
*
|
|
1124
|
+
* ```ts
|
|
1125
|
+
* import { Graph } from "effect"
|
|
1126
|
+
*
|
|
1127
|
+
* const left = Graph.directed<{ id: string }, string>((mutable) => {
|
|
1128
|
+
* const a = Graph.addNode(mutable, { id: "A" })
|
|
1129
|
+
* const b = Graph.addNode(mutable, { id: "B" })
|
|
1130
|
+
* Graph.addEdge(mutable, a, b, "A-B")
|
|
1131
|
+
* })
|
|
1132
|
+
*
|
|
1133
|
+
* const right = Graph.directed<{ id: string }, string>((mutable) => {
|
|
1134
|
+
* const b = Graph.addNode(mutable, { id: "B" })
|
|
1135
|
+
* const c = Graph.addNode(mutable, { id: "C" })
|
|
1136
|
+
* Graph.addEdge(mutable, b, c, "B-C")
|
|
1137
|
+
* })
|
|
1138
|
+
*
|
|
1139
|
+
* const result = Graph.compose(left, right, {
|
|
1140
|
+
* nodeIdentity: (node) => node.id
|
|
1141
|
+
* })
|
|
1142
|
+
*
|
|
1143
|
+
* console.log(Graph.nodeCount(result)) // 3
|
|
1144
|
+
* console.log(Graph.edgeCount(result)) // 2
|
|
1145
|
+
* ```
|
|
1146
|
+
*
|
|
1147
|
+
* @category set operations
|
|
1148
|
+
* @since 4.0.0
|
|
1149
|
+
*/
|
|
1150
|
+
N>()
|
|
1151
|
+
const edges = buildEdgeMap(self, selfMaps, getEdgeIdentity)
|
|
1152
|
+
|
|
1153
|
+
for (const [identity, data] of selfMaps.byIdentity) {
|
|
1154
|
+
MutableHashMap.set(nodes, identity, data)
|
|
1155
|
+
}
|
|
1156
|
+
for (const [identity, data] of thatMaps.byIdentity) {
|
|
1157
|
+
MutableHashMap.set(nodes, identity, data)
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
for (const [identity, data] of buildEdgeMap(that, thatMaps, getEdgeIdentity)) {
|
|
1161
|
+
MutableHashMap.set(edges, identity, data)
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
return make(self.type)</**
|
|
1165
|
+
* Composes two graphs, merging nodes by identity.
|
|
1166
|
+
*
|
|
1167
|
+
* **Details**
|
|
1168
|
+
*
|
|
1169
|
+
* Nodes and edges present in both graphs use data from `that`. The result has
|
|
1170
|
+
* the same graph kind as `self`. Throws a `GraphError` when the graph kinds do
|
|
1171
|
+
* not match. `nodeIdentity` and `edgeIdentity` default to the complete node and
|
|
1172
|
+
* edge data. Edge identity also includes the endpoint identities.
|
|
1173
|
+
*
|
|
1174
|
+
* `G1 ∪ G2 = {V1 ∪ V2, E1 ∪ E2}`
|
|
1175
|
+
*
|
|
1176
|
+
* **Gotchas**
|
|
1177
|
+
*
|
|
1178
|
+
* Nodes with equal identities in one input graph are coalesced. The last node
|
|
1179
|
+
* supplies the data, and redirected edges can collapse or become self-loops.
|
|
1180
|
+
* Parallel edges with equal identities are also coalesced, with the last edge
|
|
1181
|
+
* supplying the data.
|
|
1182
|
+
*
|
|
1183
|
+
* **Example** (Combining graphs)
|
|
1184
|
+
*
|
|
1185
|
+
* ```ts
|
|
1186
|
+
* import { Graph } from "effect"
|
|
1187
|
+
*
|
|
1188
|
+
* const left = Graph.directed<{ id: string }, string>((mutable) => {
|
|
1189
|
+
* const a = Graph.addNode(mutable, { id: "A" })
|
|
1190
|
+
* const b = Graph.addNode(mutable, { id: "B" })
|
|
1191
|
+
* Graph.addEdge(mutable, a, b, "A-B")
|
|
1192
|
+
* })
|
|
1193
|
+
*
|
|
1194
|
+
* const right = Graph.directed<{ id: string }, string>((mutable) => {
|
|
1195
|
+
* const b = Graph.addNode(mutable, { id: "B" })
|
|
1196
|
+
* const c = Graph.addNode(mutable, { id: "C" })
|
|
1197
|
+
* Graph.addEdge(mutable, b, c, "B-C")
|
|
1198
|
+
* })
|
|
1199
|
+
*
|
|
1200
|
+
* const result = Graph.compose(left, right, {
|
|
1201
|
+
* nodeIdentity: (node) => node.id
|
|
1202
|
+
* })
|
|
1203
|
+
*
|
|
1204
|
+
* console.log(Graph.nodeCount(result)) // 3
|
|
1205
|
+
* console.log(Graph.edgeCount(result)) // 2
|
|
1206
|
+
* ```
|
|
1207
|
+
*
|
|
1208
|
+
* @category set operations
|
|
1209
|
+
* @since 4.0.0
|
|
1210
|
+
*/
|
|
1211
|
+
N, /**
|
|
1212
|
+
* Composes two graphs, merging nodes by identity.
|
|
1213
|
+
*
|
|
1214
|
+
* **Details**
|
|
1215
|
+
*
|
|
1216
|
+
* Nodes and edges present in both graphs use data from `that`. The result has
|
|
1217
|
+
* the same graph kind as `self`. Throws a `GraphError` when the graph kinds do
|
|
1218
|
+
* not match. `nodeIdentity` and `edgeIdentity` default to the complete node and
|
|
1219
|
+
* edge data. Edge identity also includes the endpoint identities.
|
|
1220
|
+
*
|
|
1221
|
+
* `G1 ∪ G2 = {V1 ∪ V2, E1 ∪ E2}`
|
|
1222
|
+
*
|
|
1223
|
+
* **Gotchas**
|
|
1224
|
+
*
|
|
1225
|
+
* Nodes with equal identities in one input graph are coalesced. The last node
|
|
1226
|
+
* supplies the data, and redirected edges can collapse or become self-loops.
|
|
1227
|
+
* Parallel edges with equal identities are also coalesced, with the last edge
|
|
1228
|
+
* supplying the data.
|
|
1229
|
+
*
|
|
1230
|
+
* **Example** (Combining graphs)
|
|
1231
|
+
*
|
|
1232
|
+
* ```ts
|
|
1233
|
+
* import { Graph } from "effect"
|
|
1234
|
+
*
|
|
1235
|
+
* const left = Graph.directed<{ id: string }, string>((mutable) => {
|
|
1236
|
+
* const a = Graph.addNode(mutable, { id: "A" })
|
|
1237
|
+
* const b = Graph.addNode(mutable, { id: "B" })
|
|
1238
|
+
* Graph.addEdge(mutable, a, b, "A-B")
|
|
1239
|
+
* })
|
|
1240
|
+
*
|
|
1241
|
+
* const right = Graph.directed<{ id: string }, string>((mutable) => {
|
|
1242
|
+
* const b = Graph.addNode(mutable, { id: "B" })
|
|
1243
|
+
* const c = Graph.addNode(mutable, { id: "C" })
|
|
1244
|
+
* Graph.addEdge(mutable, b, c, "B-C")
|
|
1245
|
+
* })
|
|
1246
|
+
*
|
|
1247
|
+
* const result = Graph.compose(left, right, {
|
|
1248
|
+
* nodeIdentity: (node) => node.id
|
|
1249
|
+
* })
|
|
1250
|
+
*
|
|
1251
|
+
* console.log(Graph.nodeCount(result)) // 3
|
|
1252
|
+
* console.log(Graph.edgeCount(result)) // 2
|
|
1253
|
+
* ```
|
|
1254
|
+
*
|
|
1255
|
+
* @category set operations
|
|
1256
|
+
* @since 4.0.0
|
|
1257
|
+
*/
|
|
1258
|
+
E>((mutable) => {
|
|
1259
|
+
const indexByIdentity = addNodesByIdentity(mutable, nodes)
|
|
1260
|
+
|
|
1261
|
+
for (const [identity, data] of edges) {
|
|
1262
|
+
addEdgeByIdentity(mutable, indexByIdentity, identity, data)
|
|
1263
|
+
}
|
|
1264
|
+
});
|
|
1265
|
+
}
|
|
1266
|
+
)
|
|
1267
|
+
|
|
1268
|
+
/**
|
|
1269
|
+
* Returns the intersection of two graphs, matching nodes by identity.
|
|
1270
|
+
*
|
|
1271
|
+
* **Details**
|
|
1272
|
+
*
|
|
1273
|
+
* Node data comes from `self`, and edge data comes from `that`. The result has
|
|
1274
|
+
* the same graph kind as `self`. Throws a `GraphError` when the graph kinds do
|
|
1275
|
+
* not match. `nodeIdentity` and `edgeIdentity` default to the complete node and
|
|
1276
|
+
* edge data. Edge identity also includes the endpoint identities.
|
|
1277
|
+
*
|
|
1278
|
+
* `G1 ∩ G2 = {V1 ∩ V2, E1 ∩ E2}`
|
|
1279
|
+
*
|
|
1280
|
+
* **Gotchas**
|
|
1281
|
+
*
|
|
1282
|
+
* Nodes with equal identities in one input graph are coalesced. The last node
|
|
1283
|
+
* supplies the data, and redirected edges can collapse or become self-loops.
|
|
1284
|
+
* The result contains at most one edge for each shared edge identity.
|
|
1285
|
+
*
|
|
1286
|
+
* **Example** (Finding shared structure)
|
|
1287
|
+
*
|
|
1288
|
+
* ```ts
|
|
1289
|
+
* import { Graph } from "effect"
|
|
1290
|
+
*
|
|
1291
|
+
* const left = Graph.directed<string, string>((mutable) => {
|
|
1292
|
+
* const a = Graph.addNode(mutable, "A")
|
|
1293
|
+
* const b = Graph.addNode(mutable, "B")
|
|
1294
|
+
* Graph.addEdge(mutable, a, b, "shared")
|
|
1295
|
+
* })
|
|
1296
|
+
*
|
|
1297
|
+
* const right = Graph.directed<string, string>((mutable) => {
|
|
1298
|
+
* const a = Graph.addNode(mutable, "A")
|
|
1299
|
+
* const b = Graph.addNode(mutable, "B")
|
|
1300
|
+
* Graph.addEdge(mutable, a, b, "shared")
|
|
1301
|
+
* })
|
|
1302
|
+
*
|
|
1303
|
+
* const result = Graph.intersection(left, right)
|
|
1304
|
+
*
|
|
1305
|
+
* console.log(Graph.nodeCount(result)) // 2
|
|
1306
|
+
* console.log(Graph.edgeCount(result)) // 1
|
|
1307
|
+
* ```
|
|
1308
|
+
*
|
|
1309
|
+
* @category set operations
|
|
1310
|
+
* @since 4.0.0
|
|
1311
|
+
*/
|
|
1312
|
+
export const intersection: {
|
|
1313
|
+
/**
|
|
1314
|
+
* Returns the intersection of two graphs, matching nodes by identity.
|
|
1315
|
+
*
|
|
1316
|
+
* **Details**
|
|
1317
|
+
*
|
|
1318
|
+
* Node data comes from `self`, and edge data comes from `that`. The result has
|
|
1319
|
+
* the same graph kind as `self`. Throws a `GraphError` when the graph kinds do
|
|
1320
|
+
* not match. `nodeIdentity` and `edgeIdentity` default to the complete node and
|
|
1321
|
+
* edge data. Edge identity also includes the endpoint identities.
|
|
1322
|
+
*
|
|
1323
|
+
* `G1 ∩ G2 = {V1 ∩ V2, E1 ∩ E2}`
|
|
1324
|
+
*
|
|
1325
|
+
* **Gotchas**
|
|
1326
|
+
*
|
|
1327
|
+
* Nodes with equal identities in one input graph are coalesced. The last node
|
|
1328
|
+
* supplies the data, and redirected edges can collapse or become self-loops.
|
|
1329
|
+
* The result contains at most one edge for each shared edge identity.
|
|
1330
|
+
*
|
|
1331
|
+
* **Example** (Finding shared structure)
|
|
1332
|
+
*
|
|
1333
|
+
* ```ts
|
|
1334
|
+
* import { Graph } from "effect"
|
|
1335
|
+
*
|
|
1336
|
+
* const left = Graph.directed<string, string>((mutable) => {
|
|
1337
|
+
* const a = Graph.addNode(mutable, "A")
|
|
1338
|
+
* const b = Graph.addNode(mutable, "B")
|
|
1339
|
+
* Graph.addEdge(mutable, a, b, "shared")
|
|
1340
|
+
* })
|
|
1341
|
+
*
|
|
1342
|
+
* const right = Graph.directed<string, string>((mutable) => {
|
|
1343
|
+
* const a = Graph.addNode(mutable, "A")
|
|
1344
|
+
* const b = Graph.addNode(mutable, "B")
|
|
1345
|
+
* Graph.addEdge(mutable, a, b, "shared")
|
|
1346
|
+
* })
|
|
1347
|
+
*
|
|
1348
|
+
* const result = Graph.intersection(left, right)
|
|
1349
|
+
*
|
|
1350
|
+
* console.log(Graph.nodeCount(result)) // 2
|
|
1351
|
+
* console.log(Graph.edgeCount(result)) // 1
|
|
1352
|
+
* ```
|
|
1353
|
+
*
|
|
1354
|
+
* @category set operations
|
|
1355
|
+
* @since 4.0.0
|
|
1356
|
+
*/
|
|
1357
|
+
<N, E, T extends Kind = "directed", NI = N, EI = E>(that: Graph<N, E, T>, options?: IdentityOptions<N, E, NI, EI>): (self: Graph<N, E, NoInfer<T>>) => Graph<N, E, T>
|
|
1358
|
+
/**
|
|
1359
|
+
* Returns the intersection of two graphs, matching nodes by identity.
|
|
1360
|
+
*
|
|
1361
|
+
* **Details**
|
|
1362
|
+
*
|
|
1363
|
+
* Node data comes from `self`, and edge data comes from `that`. The result has
|
|
1364
|
+
* the same graph kind as `self`. Throws a `GraphError` when the graph kinds do
|
|
1365
|
+
* not match. `nodeIdentity` and `edgeIdentity` default to the complete node and
|
|
1366
|
+
* edge data. Edge identity also includes the endpoint identities.
|
|
1367
|
+
*
|
|
1368
|
+
* `G1 ∩ G2 = {V1 ∩ V2, E1 ∩ E2}`
|
|
1369
|
+
*
|
|
1370
|
+
* **Gotchas**
|
|
1371
|
+
*
|
|
1372
|
+
* Nodes with equal identities in one input graph are coalesced. The last node
|
|
1373
|
+
* supplies the data, and redirected edges can collapse or become self-loops.
|
|
1374
|
+
* The result contains at most one edge for each shared edge identity.
|
|
1375
|
+
*
|
|
1376
|
+
* **Example** (Finding shared structure)
|
|
1377
|
+
*
|
|
1378
|
+
* ```ts
|
|
1379
|
+
* import { Graph } from "effect"
|
|
1380
|
+
*
|
|
1381
|
+
* const left = Graph.directed<string, string>((mutable) => {
|
|
1382
|
+
* const a = Graph.addNode(mutable, "A")
|
|
1383
|
+
* const b = Graph.addNode(mutable, "B")
|
|
1384
|
+
* Graph.addEdge(mutable, a, b, "shared")
|
|
1385
|
+
* })
|
|
1386
|
+
*
|
|
1387
|
+
* const right = Graph.directed<string, string>((mutable) => {
|
|
1388
|
+
* const a = Graph.addNode(mutable, "A")
|
|
1389
|
+
* const b = Graph.addNode(mutable, "B")
|
|
1390
|
+
* Graph.addEdge(mutable, a, b, "shared")
|
|
1391
|
+
* })
|
|
1392
|
+
*
|
|
1393
|
+
* const result = Graph.intersection(left, right)
|
|
1394
|
+
*
|
|
1395
|
+
* console.log(Graph.nodeCount(result)) // 2
|
|
1396
|
+
* console.log(Graph.edgeCount(result)) // 1
|
|
1397
|
+
* ```
|
|
1398
|
+
*
|
|
1399
|
+
* @category set operations
|
|
1400
|
+
* @since 4.0.0
|
|
1401
|
+
*/
|
|
1402
|
+
<N, E, T extends Kind = "directed", NI = N, EI = E>(
|
|
1403
|
+
self: Graph<N, E, T>,
|
|
1404
|
+
that: Graph<N, E, NoInfer<T>>,
|
|
1405
|
+
options?: IdentityOptions<N, E, NI, EI>
|
|
1406
|
+
): Graph<N, E, T>
|
|
1407
|
+
} = dual((args) => isGraph(args[0]) && isGraph(args[1]), <N, E, T extends Kind, NI, EI>(
|
|
1408
|
+
self: Graph<N, E, T>,
|
|
1409
|
+
that: Graph<N, E, T>,
|
|
1410
|
+
options?: IdentityOptions<N, E, NI, EI>
|
|
1411
|
+
): Graph<N, E, T> => {
|
|
1412
|
+
assertSameKind(self, that)
|
|
1413
|
+
const thatImpl = graphImpl(that)
|
|
1414
|
+
const getNodeIdentity = options?.nodeIdentity ?? ((node: N) => node as unknown as NI)
|
|
1415
|
+
const getEdgeIdentity = options?.edgeIdentity ?? ((edge: E) => edge as unknown as EI)
|
|
1416
|
+
const selfMaps = buildNodeMaps(self, getNodeIdentity)
|
|
1417
|
+
const thatMaps = buildNodeMaps(that, getNodeIdentity)
|
|
1418
|
+
const nodes = MutableHashMap.empty</**
|
|
1419
|
+
* Returns the intersection of two graphs, matching nodes by identity.
|
|
1420
|
+
*
|
|
1421
|
+
* **Details**
|
|
1422
|
+
*
|
|
1423
|
+
* Node data comes from `self`, and edge data comes from `that`. The result has
|
|
1424
|
+
* the same graph kind as `self`. Throws a `GraphError` when the graph kinds do
|
|
1425
|
+
* not match. `nodeIdentity` and `edgeIdentity` default to the complete node and
|
|
1426
|
+
* edge data. Edge identity also includes the endpoint identities.
|
|
1427
|
+
*
|
|
1428
|
+
* `G1 ∩ G2 = {V1 ∩ V2, E1 ∩ E2}`
|
|
1429
|
+
*
|
|
1430
|
+
* **Gotchas**
|
|
1431
|
+
*
|
|
1432
|
+
* Nodes with equal identities in one input graph are coalesced. The last node
|
|
1433
|
+
* supplies the data, and redirected edges can collapse or become self-loops.
|
|
1434
|
+
* The result contains at most one edge for each shared edge identity.
|
|
1435
|
+
*
|
|
1436
|
+
* **Example** (Finding shared structure)
|
|
1437
|
+
*
|
|
1438
|
+
* ```ts
|
|
1439
|
+
* import { Graph } from "effect"
|
|
1440
|
+
*
|
|
1441
|
+
* const left = Graph.directed<string, string>((mutable) => {
|
|
1442
|
+
* const a = Graph.addNode(mutable, "A")
|
|
1443
|
+
* const b = Graph.addNode(mutable, "B")
|
|
1444
|
+
* Graph.addEdge(mutable, a, b, "shared")
|
|
1445
|
+
* })
|
|
1446
|
+
*
|
|
1447
|
+
* const right = Graph.directed<string, string>((mutable) => {
|
|
1448
|
+
* const a = Graph.addNode(mutable, "A")
|
|
1449
|
+
* const b = Graph.addNode(mutable, "B")
|
|
1450
|
+
* Graph.addEdge(mutable, a, b, "shared")
|
|
1451
|
+
* })
|
|
1452
|
+
*
|
|
1453
|
+
* const result = Graph.intersection(left, right)
|
|
1454
|
+
*
|
|
1455
|
+
* console.log(Graph.nodeCount(result)) // 2
|
|
1456
|
+
* console.log(Graph.edgeCount(result)) // 1
|
|
1457
|
+
* ```
|
|
1458
|
+
*
|
|
1459
|
+
* @category set operations
|
|
1460
|
+
* @since 4.0.0
|
|
1461
|
+
*/
|
|
1462
|
+
NI, /**
|
|
1463
|
+
* Returns the intersection of two graphs, matching nodes by identity.
|
|
1464
|
+
*
|
|
1465
|
+
* **Details**
|
|
1466
|
+
*
|
|
1467
|
+
* Node data comes from `self`, and edge data comes from `that`. The result has
|
|
1468
|
+
* the same graph kind as `self`. Throws a `GraphError` when the graph kinds do
|
|
1469
|
+
* not match. `nodeIdentity` and `edgeIdentity` default to the complete node and
|
|
1470
|
+
* edge data. Edge identity also includes the endpoint identities.
|
|
1471
|
+
*
|
|
1472
|
+
* `G1 ∩ G2 = {V1 ∩ V2, E1 ∩ E2}`
|
|
1473
|
+
*
|
|
1474
|
+
* **Gotchas**
|
|
1475
|
+
*
|
|
1476
|
+
* Nodes with equal identities in one input graph are coalesced. The last node
|
|
1477
|
+
* supplies the data, and redirected edges can collapse or become self-loops.
|
|
1478
|
+
* The result contains at most one edge for each shared edge identity.
|
|
1479
|
+
*
|
|
1480
|
+
* **Example** (Finding shared structure)
|
|
1481
|
+
*
|
|
1482
|
+
* ```ts
|
|
1483
|
+
* import { Graph } from "effect"
|
|
1484
|
+
*
|
|
1485
|
+
* const left = Graph.directed<string, string>((mutable) => {
|
|
1486
|
+
* const a = Graph.addNode(mutable, "A")
|
|
1487
|
+
* const b = Graph.addNode(mutable, "B")
|
|
1488
|
+
* Graph.addEdge(mutable, a, b, "shared")
|
|
1489
|
+
* })
|
|
1490
|
+
*
|
|
1491
|
+
* const right = Graph.directed<string, string>((mutable) => {
|
|
1492
|
+
* const a = Graph.addNode(mutable, "A")
|
|
1493
|
+
* const b = Graph.addNode(mutable, "B")
|
|
1494
|
+
* Graph.addEdge(mutable, a, b, "shared")
|
|
1495
|
+
* })
|
|
1496
|
+
*
|
|
1497
|
+
* const result = Graph.intersection(left, right)
|
|
1498
|
+
*
|
|
1499
|
+
* console.log(Graph.nodeCount(result)) // 2
|
|
1500
|
+
* console.log(Graph.edgeCount(result)) // 1
|
|
1501
|
+
* ```
|
|
1502
|
+
*
|
|
1503
|
+
* @category set operations
|
|
1504
|
+
* @since 4.0.0
|
|
1505
|
+
*/
|
|
1506
|
+
N>()
|
|
1507
|
+
const selfEdges = buildEdgeMap(self, selfMaps, getEdgeIdentity)
|
|
1508
|
+
const thatEdges = MutableHashMap.empty</**
|
|
1509
|
+
* Returns the intersection of two graphs, matching nodes by identity.
|
|
1510
|
+
*
|
|
1511
|
+
* **Details**
|
|
1512
|
+
*
|
|
1513
|
+
* Node data comes from `self`, and edge data comes from `that`. The result has
|
|
1514
|
+
* the same graph kind as `self`. Throws a `GraphError` when the graph kinds do
|
|
1515
|
+
* not match. `nodeIdentity` and `edgeIdentity` default to the complete node and
|
|
1516
|
+
* edge data. Edge identity also includes the endpoint identities.
|
|
1517
|
+
*
|
|
1518
|
+
* `G1 ∩ G2 = {V1 ∩ V2, E1 ∩ E2}`
|
|
1519
|
+
*
|
|
1520
|
+
* **Gotchas**
|
|
1521
|
+
*
|
|
1522
|
+
* Nodes with equal identities in one input graph are coalesced. The last node
|
|
1523
|
+
* supplies the data, and redirected edges can collapse or become self-loops.
|
|
1524
|
+
* The result contains at most one edge for each shared edge identity.
|
|
1525
|
+
*
|
|
1526
|
+
* **Example** (Finding shared structure)
|
|
1527
|
+
*
|
|
1528
|
+
* ```ts
|
|
1529
|
+
* import { Graph } from "effect"
|
|
1530
|
+
*
|
|
1531
|
+
* const left = Graph.directed<string, string>((mutable) => {
|
|
1532
|
+
* const a = Graph.addNode(mutable, "A")
|
|
1533
|
+
* const b = Graph.addNode(mutable, "B")
|
|
1534
|
+
* Graph.addEdge(mutable, a, b, "shared")
|
|
1535
|
+
* })
|
|
1536
|
+
*
|
|
1537
|
+
* const right = Graph.directed<string, string>((mutable) => {
|
|
1538
|
+
* const a = Graph.addNode(mutable, "A")
|
|
1539
|
+
* const b = Graph.addNode(mutable, "B")
|
|
1540
|
+
* Graph.addEdge(mutable, a, b, "shared")
|
|
1541
|
+
* })
|
|
1542
|
+
*
|
|
1543
|
+
* const result = Graph.intersection(left, right)
|
|
1544
|
+
*
|
|
1545
|
+
* console.log(Graph.nodeCount(result)) // 2
|
|
1546
|
+
* console.log(Graph.edgeCount(result)) // 1
|
|
1547
|
+
* ```
|
|
1548
|
+
*
|
|
1549
|
+
* @category set operations
|
|
1550
|
+
* @since 4.0.0
|
|
1551
|
+
*/
|
|
1552
|
+
EdgeIdentity<NI, EI>, /**
|
|
1553
|
+
* Returns the intersection of two graphs, matching nodes by identity.
|
|
1554
|
+
*
|
|
1555
|
+
* **Details**
|
|
1556
|
+
*
|
|
1557
|
+
* Node data comes from `self`, and edge data comes from `that`. The result has
|
|
1558
|
+
* the same graph kind as `self`. Throws a `GraphError` when the graph kinds do
|
|
1559
|
+
* not match. `nodeIdentity` and `edgeIdentity` default to the complete node and
|
|
1560
|
+
* edge data. Edge identity also includes the endpoint identities.
|
|
1561
|
+
*
|
|
1562
|
+
* `G1 ∩ G2 = {V1 ∩ V2, E1 ∩ E2}`
|
|
1563
|
+
*
|
|
1564
|
+
* **Gotchas**
|
|
1565
|
+
*
|
|
1566
|
+
* Nodes with equal identities in one input graph are coalesced. The last node
|
|
1567
|
+
* supplies the data, and redirected edges can collapse or become self-loops.
|
|
1568
|
+
* The result contains at most one edge for each shared edge identity.
|
|
1569
|
+
*
|
|
1570
|
+
* **Example** (Finding shared structure)
|
|
1571
|
+
*
|
|
1572
|
+
* ```ts
|
|
1573
|
+
* import { Graph } from "effect"
|
|
1574
|
+
*
|
|
1575
|
+
* const left = Graph.directed<string, string>((mutable) => {
|
|
1576
|
+
* const a = Graph.addNode(mutable, "A")
|
|
1577
|
+
* const b = Graph.addNode(mutable, "B")
|
|
1578
|
+
* Graph.addEdge(mutable, a, b, "shared")
|
|
1579
|
+
* })
|
|
1580
|
+
*
|
|
1581
|
+
* const right = Graph.directed<string, string>((mutable) => {
|
|
1582
|
+
* const a = Graph.addNode(mutable, "A")
|
|
1583
|
+
* const b = Graph.addNode(mutable, "B")
|
|
1584
|
+
* Graph.addEdge(mutable, a, b, "shared")
|
|
1585
|
+
* })
|
|
1586
|
+
*
|
|
1587
|
+
* const result = Graph.intersection(left, right)
|
|
1588
|
+
*
|
|
1589
|
+
* console.log(Graph.nodeCount(result)) // 2
|
|
1590
|
+
* console.log(Graph.edgeCount(result)) // 1
|
|
1591
|
+
* ```
|
|
1592
|
+
*
|
|
1593
|
+
* @category set operations
|
|
1594
|
+
* @since 4.0.0
|
|
1595
|
+
*/
|
|
1596
|
+
E>()
|
|
1597
|
+
|
|
1598
|
+
for (const [identity, data] of selfMaps.byIdentity) {
|
|
1599
|
+
if (MutableHashMap.has(thatMaps.byIdentity, identity)) {
|
|
1600
|
+
MutableHashMap.set(nodes, identity, data)
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
for (const edge of thatImpl.edges.values()) {
|
|
1605
|
+
const sourceIdentity = nodeIdentityAt(thatMaps, edge.source)
|
|
1606
|
+
const targetIdentity = nodeIdentityAt(thatMaps, edge.target)
|
|
1607
|
+
if (MutableHashMap.has(nodes, sourceIdentity) && MutableHashMap.has(nodes, targetIdentity)) {
|
|
1608
|
+
const edgeIdentity = new EdgeIdentity(that.type, sourceIdentity, targetIdentity, getEdgeIdentity(edge.data))
|
|
1609
|
+
MutableHashMap.set(thatEdges, edgeIdentity, edge.data)
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
return make(self.type)</**
|
|
1614
|
+
* Returns the intersection of two graphs, matching nodes by identity.
|
|
1615
|
+
*
|
|
1616
|
+
* **Details**
|
|
1617
|
+
*
|
|
1618
|
+
* Node data comes from `self`, and edge data comes from `that`. The result has
|
|
1619
|
+
* the same graph kind as `self`. Throws a `GraphError` when the graph kinds do
|
|
1620
|
+
* not match. `nodeIdentity` and `edgeIdentity` default to the complete node and
|
|
1621
|
+
* edge data. Edge identity also includes the endpoint identities.
|
|
1622
|
+
*
|
|
1623
|
+
* `G1 ∩ G2 = {V1 ∩ V2, E1 ∩ E2}`
|
|
1624
|
+
*
|
|
1625
|
+
* **Gotchas**
|
|
1626
|
+
*
|
|
1627
|
+
* Nodes with equal identities in one input graph are coalesced. The last node
|
|
1628
|
+
* supplies the data, and redirected edges can collapse or become self-loops.
|
|
1629
|
+
* The result contains at most one edge for each shared edge identity.
|
|
1630
|
+
*
|
|
1631
|
+
* **Example** (Finding shared structure)
|
|
1632
|
+
*
|
|
1633
|
+
* ```ts
|
|
1634
|
+
* import { Graph } from "effect"
|
|
1635
|
+
*
|
|
1636
|
+
* const left = Graph.directed<string, string>((mutable) => {
|
|
1637
|
+
* const a = Graph.addNode(mutable, "A")
|
|
1638
|
+
* const b = Graph.addNode(mutable, "B")
|
|
1639
|
+
* Graph.addEdge(mutable, a, b, "shared")
|
|
1640
|
+
* })
|
|
1641
|
+
*
|
|
1642
|
+
* const right = Graph.directed<string, string>((mutable) => {
|
|
1643
|
+
* const a = Graph.addNode(mutable, "A")
|
|
1644
|
+
* const b = Graph.addNode(mutable, "B")
|
|
1645
|
+
* Graph.addEdge(mutable, a, b, "shared")
|
|
1646
|
+
* })
|
|
1647
|
+
*
|
|
1648
|
+
* const result = Graph.intersection(left, right)
|
|
1649
|
+
*
|
|
1650
|
+
* console.log(Graph.nodeCount(result)) // 2
|
|
1651
|
+
* console.log(Graph.edgeCount(result)) // 1
|
|
1652
|
+
* ```
|
|
1653
|
+
*
|
|
1654
|
+
* @category set operations
|
|
1655
|
+
* @since 4.0.0
|
|
1656
|
+
*/
|
|
1657
|
+
N, /**
|
|
1658
|
+
* Returns the intersection of two graphs, matching nodes by identity.
|
|
1659
|
+
*
|
|
1660
|
+
* **Details**
|
|
1661
|
+
*
|
|
1662
|
+
* Node data comes from `self`, and edge data comes from `that`. The result has
|
|
1663
|
+
* the same graph kind as `self`. Throws a `GraphError` when the graph kinds do
|
|
1664
|
+
* not match. `nodeIdentity` and `edgeIdentity` default to the complete node and
|
|
1665
|
+
* edge data. Edge identity also includes the endpoint identities.
|
|
1666
|
+
*
|
|
1667
|
+
* `G1 ∩ G2 = {V1 ∩ V2, E1 ∩ E2}`
|
|
1668
|
+
*
|
|
1669
|
+
* **Gotchas**
|
|
1670
|
+
*
|
|
1671
|
+
* Nodes with equal identities in one input graph are coalesced. The last node
|
|
1672
|
+
* supplies the data, and redirected edges can collapse or become self-loops.
|
|
1673
|
+
* The result contains at most one edge for each shared edge identity.
|
|
1674
|
+
*
|
|
1675
|
+
* **Example** (Finding shared structure)
|
|
1676
|
+
*
|
|
1677
|
+
* ```ts
|
|
1678
|
+
* import { Graph } from "effect"
|
|
1679
|
+
*
|
|
1680
|
+
* const left = Graph.directed<string, string>((mutable) => {
|
|
1681
|
+
* const a = Graph.addNode(mutable, "A")
|
|
1682
|
+
* const b = Graph.addNode(mutable, "B")
|
|
1683
|
+
* Graph.addEdge(mutable, a, b, "shared")
|
|
1684
|
+
* })
|
|
1685
|
+
*
|
|
1686
|
+
* const right = Graph.directed<string, string>((mutable) => {
|
|
1687
|
+
* const a = Graph.addNode(mutable, "A")
|
|
1688
|
+
* const b = Graph.addNode(mutable, "B")
|
|
1689
|
+
* Graph.addEdge(mutable, a, b, "shared")
|
|
1690
|
+
* })
|
|
1691
|
+
*
|
|
1692
|
+
* const result = Graph.intersection(left, right)
|
|
1693
|
+
*
|
|
1694
|
+
* console.log(Graph.nodeCount(result)) // 2
|
|
1695
|
+
* console.log(Graph.edgeCount(result)) // 1
|
|
1696
|
+
* ```
|
|
1697
|
+
*
|
|
1698
|
+
* @category set operations
|
|
1699
|
+
* @since 4.0.0
|
|
1700
|
+
*/
|
|
1701
|
+
E>((mutable) => {
|
|
1702
|
+
const indexByIdentity = addNodesByIdentity(mutable, nodes)
|
|
1703
|
+
|
|
1704
|
+
for (const [identity, data] of thatEdges) {
|
|
1705
|
+
if (MutableHashMap.has(selfEdges, identity)) {
|
|
1706
|
+
addEdgeByIdentity(mutable, indexByIdentity, identity, data)
|
|
1707
|
+
}
|
|
1708
|
+
}
|
|
1709
|
+
});
|
|
1710
|
+
})
|
|
1711
|
+
|
|
1712
|
+
/**
|
|
1713
|
+
* Returns `self` without edges also present in `that`.
|
|
1714
|
+
*
|
|
1715
|
+
* **Details**
|
|
1716
|
+
*
|
|
1717
|
+
* All nodes from `self` are preserved. Edges are matched by endpoint and edge
|
|
1718
|
+
* identities. The result has the same graph kind as `self`. Throws a
|
|
1719
|
+
* `GraphError` when the graph kinds do not match. `nodeIdentity` and
|
|
1720
|
+
* `edgeIdentity` default to the complete node and edge data.
|
|
1721
|
+
*
|
|
1722
|
+
* `G1 \ G2 = {V1, E1 \ E2}`
|
|
1723
|
+
*
|
|
1724
|
+
* **Gotchas**
|
|
1725
|
+
*
|
|
1726
|
+
* Nodes with equal identities in one input graph are coalesced. The last node
|
|
1727
|
+
* supplies the data, and redirected edges can collapse or become self-loops.
|
|
1728
|
+
* If `that` contains an edge identity, every parallel edge with that identity
|
|
1729
|
+
* is removed from `self`.
|
|
1730
|
+
*
|
|
1731
|
+
* **Example** (Removing shared edges)
|
|
1732
|
+
*
|
|
1733
|
+
* ```ts
|
|
1734
|
+
* import { Graph } from "effect"
|
|
1735
|
+
*
|
|
1736
|
+
* const left = Graph.directed<string, string>((mutable) => {
|
|
1737
|
+
* const a = Graph.addNode(mutable, "A")
|
|
1738
|
+
* const b = Graph.addNode(mutable, "B")
|
|
1739
|
+
* const c = Graph.addNode(mutable, "C")
|
|
1740
|
+
* Graph.addEdge(mutable, a, b, "A-B")
|
|
1741
|
+
* Graph.addEdge(mutable, b, c, "B-C")
|
|
1742
|
+
* })
|
|
1743
|
+
*
|
|
1744
|
+
* const right = Graph.directed<string, string>((mutable) => {
|
|
1745
|
+
* const b = Graph.addNode(mutable, "B")
|
|
1746
|
+
* const c = Graph.addNode(mutable, "C")
|
|
1747
|
+
* Graph.addEdge(mutable, b, c, "B-C")
|
|
1748
|
+
* })
|
|
1749
|
+
*
|
|
1750
|
+
* const result = Graph.difference(left, right)
|
|
1751
|
+
*
|
|
1752
|
+
* console.log(Graph.nodeCount(result)) // 3
|
|
1753
|
+
* console.log(Graph.edgeCount(result)) // 1
|
|
1754
|
+
* ```
|
|
1755
|
+
*
|
|
1756
|
+
* @category set operations
|
|
1757
|
+
* @since 4.0.0
|
|
1758
|
+
*/
|
|
1759
|
+
export const difference: {
|
|
1760
|
+
/**
|
|
1761
|
+
* Returns `self` without edges also present in `that`.
|
|
1762
|
+
*
|
|
1763
|
+
* **Details**
|
|
1764
|
+
*
|
|
1765
|
+
* All nodes from `self` are preserved. Edges are matched by endpoint and edge
|
|
1766
|
+
* identities. The result has the same graph kind as `self`. Throws a
|
|
1767
|
+
* `GraphError` when the graph kinds do not match. `nodeIdentity` and
|
|
1768
|
+
* `edgeIdentity` default to the complete node and edge data.
|
|
1769
|
+
*
|
|
1770
|
+
* `G1 \ G2 = {V1, E1 \ E2}`
|
|
1771
|
+
*
|
|
1772
|
+
* **Gotchas**
|
|
1773
|
+
*
|
|
1774
|
+
* Nodes with equal identities in one input graph are coalesced. The last node
|
|
1775
|
+
* supplies the data, and redirected edges can collapse or become self-loops.
|
|
1776
|
+
* If `that` contains an edge identity, every parallel edge with that identity
|
|
1777
|
+
* is removed from `self`.
|
|
1778
|
+
*
|
|
1779
|
+
* **Example** (Removing shared edges)
|
|
1780
|
+
*
|
|
1781
|
+
* ```ts
|
|
1782
|
+
* import { Graph } from "effect"
|
|
1783
|
+
*
|
|
1784
|
+
* const left = Graph.directed<string, string>((mutable) => {
|
|
1785
|
+
* const a = Graph.addNode(mutable, "A")
|
|
1786
|
+
* const b = Graph.addNode(mutable, "B")
|
|
1787
|
+
* const c = Graph.addNode(mutable, "C")
|
|
1788
|
+
* Graph.addEdge(mutable, a, b, "A-B")
|
|
1789
|
+
* Graph.addEdge(mutable, b, c, "B-C")
|
|
1790
|
+
* })
|
|
1791
|
+
*
|
|
1792
|
+
* const right = Graph.directed<string, string>((mutable) => {
|
|
1793
|
+
* const b = Graph.addNode(mutable, "B")
|
|
1794
|
+
* const c = Graph.addNode(mutable, "C")
|
|
1795
|
+
* Graph.addEdge(mutable, b, c, "B-C")
|
|
1796
|
+
* })
|
|
1797
|
+
*
|
|
1798
|
+
* const result = Graph.difference(left, right)
|
|
1799
|
+
*
|
|
1800
|
+
* console.log(Graph.nodeCount(result)) // 3
|
|
1801
|
+
* console.log(Graph.edgeCount(result)) // 1
|
|
1802
|
+
* ```
|
|
1803
|
+
*
|
|
1804
|
+
* @category set operations
|
|
1805
|
+
* @since 4.0.0
|
|
1806
|
+
*/
|
|
1807
|
+
<N, E, T extends Kind = "directed", NI = N, EI = E>(that: Graph<N, E, T>, options?: IdentityOptions<N, E, NI, EI>): (self: Graph<N, E, NoInfer<T>>) => Graph<N, E, T>
|
|
1808
|
+
/**
|
|
1809
|
+
* Returns `self` without edges also present in `that`.
|
|
1810
|
+
*
|
|
1811
|
+
* **Details**
|
|
1812
|
+
*
|
|
1813
|
+
* All nodes from `self` are preserved. Edges are matched by endpoint and edge
|
|
1814
|
+
* identities. The result has the same graph kind as `self`. Throws a
|
|
1815
|
+
* `GraphError` when the graph kinds do not match. `nodeIdentity` and
|
|
1816
|
+
* `edgeIdentity` default to the complete node and edge data.
|
|
1817
|
+
*
|
|
1818
|
+
* `G1 \ G2 = {V1, E1 \ E2}`
|
|
1819
|
+
*
|
|
1820
|
+
* **Gotchas**
|
|
1821
|
+
*
|
|
1822
|
+
* Nodes with equal identities in one input graph are coalesced. The last node
|
|
1823
|
+
* supplies the data, and redirected edges can collapse or become self-loops.
|
|
1824
|
+
* If `that` contains an edge identity, every parallel edge with that identity
|
|
1825
|
+
* is removed from `self`.
|
|
1826
|
+
*
|
|
1827
|
+
* **Example** (Removing shared edges)
|
|
1828
|
+
*
|
|
1829
|
+
* ```ts
|
|
1830
|
+
* import { Graph } from "effect"
|
|
1831
|
+
*
|
|
1832
|
+
* const left = Graph.directed<string, string>((mutable) => {
|
|
1833
|
+
* const a = Graph.addNode(mutable, "A")
|
|
1834
|
+
* const b = Graph.addNode(mutable, "B")
|
|
1835
|
+
* const c = Graph.addNode(mutable, "C")
|
|
1836
|
+
* Graph.addEdge(mutable, a, b, "A-B")
|
|
1837
|
+
* Graph.addEdge(mutable, b, c, "B-C")
|
|
1838
|
+
* })
|
|
1839
|
+
*
|
|
1840
|
+
* const right = Graph.directed<string, string>((mutable) => {
|
|
1841
|
+
* const b = Graph.addNode(mutable, "B")
|
|
1842
|
+
* const c = Graph.addNode(mutable, "C")
|
|
1843
|
+
* Graph.addEdge(mutable, b, c, "B-C")
|
|
1844
|
+
* })
|
|
1845
|
+
*
|
|
1846
|
+
* const result = Graph.difference(left, right)
|
|
1847
|
+
*
|
|
1848
|
+
* console.log(Graph.nodeCount(result)) // 3
|
|
1849
|
+
* console.log(Graph.edgeCount(result)) // 1
|
|
1850
|
+
* ```
|
|
1851
|
+
*
|
|
1852
|
+
* @category set operations
|
|
1853
|
+
* @since 4.0.0
|
|
1854
|
+
*/
|
|
1855
|
+
<N, E, T extends Kind = "directed", NI = N, EI = E>(
|
|
1856
|
+
self: Graph<N, E, T>,
|
|
1857
|
+
that: Graph<N, E, NoInfer<T>>,
|
|
1858
|
+
options?: IdentityOptions<N, E, NI, EI>
|
|
1859
|
+
): Graph<N, E, T>
|
|
1860
|
+
} = dual((args) => isGraph(args[0]) && isGraph(args[1]), <N, E, T extends Kind, NI, EI>(
|
|
1861
|
+
self: Graph<N, E, T>,
|
|
1862
|
+
that: Graph<N, E, T>,
|
|
1863
|
+
options?: IdentityOptions<N, E, NI, EI>
|
|
1864
|
+
): Graph<N, E, T> => {
|
|
1865
|
+
assertSameKind(self, that)
|
|
1866
|
+
const selfImpl = graphImpl(self)
|
|
1867
|
+
const getNodeIdentity = options?.nodeIdentity ?? ((node: N) => node as unknown as NI)
|
|
1868
|
+
const getEdgeIdentity = options?.edgeIdentity ?? ((edge: E) => edge as unknown as EI)
|
|
1869
|
+
const selfMaps = buildNodeMaps(self, getNodeIdentity)
|
|
1870
|
+
const thatMaps = buildNodeMaps(that, getNodeIdentity)
|
|
1871
|
+
const thatEdges = buildEdgeMap(that, thatMaps, getEdgeIdentity)
|
|
1872
|
+
|
|
1873
|
+
return make(self.type)</**
|
|
1874
|
+
* Returns `self` without edges also present in `that`.
|
|
1875
|
+
*
|
|
1876
|
+
* **Details**
|
|
1877
|
+
*
|
|
1878
|
+
* All nodes from `self` are preserved. Edges are matched by endpoint and edge
|
|
1879
|
+
* identities. The result has the same graph kind as `self`. Throws a
|
|
1880
|
+
* `GraphError` when the graph kinds do not match. `nodeIdentity` and
|
|
1881
|
+
* `edgeIdentity` default to the complete node and edge data.
|
|
1882
|
+
*
|
|
1883
|
+
* `G1 \ G2 = {V1, E1 \ E2}`
|
|
1884
|
+
*
|
|
1885
|
+
* **Gotchas**
|
|
1886
|
+
*
|
|
1887
|
+
* Nodes with equal identities in one input graph are coalesced. The last node
|
|
1888
|
+
* supplies the data, and redirected edges can collapse or become self-loops.
|
|
1889
|
+
* If `that` contains an edge identity, every parallel edge with that identity
|
|
1890
|
+
* is removed from `self`.
|
|
1891
|
+
*
|
|
1892
|
+
* **Example** (Removing shared edges)
|
|
1893
|
+
*
|
|
1894
|
+
* ```ts
|
|
1895
|
+
* import { Graph } from "effect"
|
|
1896
|
+
*
|
|
1897
|
+
* const left = Graph.directed<string, string>((mutable) => {
|
|
1898
|
+
* const a = Graph.addNode(mutable, "A")
|
|
1899
|
+
* const b = Graph.addNode(mutable, "B")
|
|
1900
|
+
* const c = Graph.addNode(mutable, "C")
|
|
1901
|
+
* Graph.addEdge(mutable, a, b, "A-B")
|
|
1902
|
+
* Graph.addEdge(mutable, b, c, "B-C")
|
|
1903
|
+
* })
|
|
1904
|
+
*
|
|
1905
|
+
* const right = Graph.directed<string, string>((mutable) => {
|
|
1906
|
+
* const b = Graph.addNode(mutable, "B")
|
|
1907
|
+
* const c = Graph.addNode(mutable, "C")
|
|
1908
|
+
* Graph.addEdge(mutable, b, c, "B-C")
|
|
1909
|
+
* })
|
|
1910
|
+
*
|
|
1911
|
+
* const result = Graph.difference(left, right)
|
|
1912
|
+
*
|
|
1913
|
+
* console.log(Graph.nodeCount(result)) // 3
|
|
1914
|
+
* console.log(Graph.edgeCount(result)) // 1
|
|
1915
|
+
* ```
|
|
1916
|
+
*
|
|
1917
|
+
* @category set operations
|
|
1918
|
+
* @since 4.0.0
|
|
1919
|
+
*/
|
|
1920
|
+
N, /**
|
|
1921
|
+
* Returns `self` without edges also present in `that`.
|
|
1922
|
+
*
|
|
1923
|
+
* **Details**
|
|
1924
|
+
*
|
|
1925
|
+
* All nodes from `self` are preserved. Edges are matched by endpoint and edge
|
|
1926
|
+
* identities. The result has the same graph kind as `self`. Throws a
|
|
1927
|
+
* `GraphError` when the graph kinds do not match. `nodeIdentity` and
|
|
1928
|
+
* `edgeIdentity` default to the complete node and edge data.
|
|
1929
|
+
*
|
|
1930
|
+
* `G1 \ G2 = {V1, E1 \ E2}`
|
|
1931
|
+
*
|
|
1932
|
+
* **Gotchas**
|
|
1933
|
+
*
|
|
1934
|
+
* Nodes with equal identities in one input graph are coalesced. The last node
|
|
1935
|
+
* supplies the data, and redirected edges can collapse or become self-loops.
|
|
1936
|
+
* If `that` contains an edge identity, every parallel edge with that identity
|
|
1937
|
+
* is removed from `self`.
|
|
1938
|
+
*
|
|
1939
|
+
* **Example** (Removing shared edges)
|
|
1940
|
+
*
|
|
1941
|
+
* ```ts
|
|
1942
|
+
* import { Graph } from "effect"
|
|
1943
|
+
*
|
|
1944
|
+
* const left = Graph.directed<string, string>((mutable) => {
|
|
1945
|
+
* const a = Graph.addNode(mutable, "A")
|
|
1946
|
+
* const b = Graph.addNode(mutable, "B")
|
|
1947
|
+
* const c = Graph.addNode(mutable, "C")
|
|
1948
|
+
* Graph.addEdge(mutable, a, b, "A-B")
|
|
1949
|
+
* Graph.addEdge(mutable, b, c, "B-C")
|
|
1950
|
+
* })
|
|
1951
|
+
*
|
|
1952
|
+
* const right = Graph.directed<string, string>((mutable) => {
|
|
1953
|
+
* const b = Graph.addNode(mutable, "B")
|
|
1954
|
+
* const c = Graph.addNode(mutable, "C")
|
|
1955
|
+
* Graph.addEdge(mutable, b, c, "B-C")
|
|
1956
|
+
* })
|
|
1957
|
+
*
|
|
1958
|
+
* const result = Graph.difference(left, right)
|
|
1959
|
+
*
|
|
1960
|
+
* console.log(Graph.nodeCount(result)) // 3
|
|
1961
|
+
* console.log(Graph.edgeCount(result)) // 1
|
|
1962
|
+
* ```
|
|
1963
|
+
*
|
|
1964
|
+
* @category set operations
|
|
1965
|
+
* @since 4.0.0
|
|
1966
|
+
*/
|
|
1967
|
+
E>((mutable) => {
|
|
1968
|
+
const indexByIdentity = addNodesByIdentity(mutable, selfMaps.byIdentity)
|
|
1969
|
+
|
|
1970
|
+
for (const edge of selfImpl.edges.values()) {
|
|
1971
|
+
const sourceIdentity = nodeIdentityAt(selfMaps, edge.source)
|
|
1972
|
+
const targetIdentity = nodeIdentityAt(selfMaps, edge.target)
|
|
1973
|
+
const edgeIdentity = new EdgeIdentity(self.type, sourceIdentity, targetIdentity, getEdgeIdentity(edge.data))
|
|
1974
|
+
if (!MutableHashMap.has(thatEdges, edgeIdentity)) {
|
|
1975
|
+
addEdgeByIdentity(mutable, indexByIdentity, edgeIdentity, edge.data)
|
|
1976
|
+
}
|
|
1977
|
+
}
|
|
1978
|
+
});
|
|
1979
|
+
})
|
|
1980
|
+
|
|
1981
|
+
/**
|
|
1982
|
+
* Returns edges present in exactly one of two graphs.
|
|
1983
|
+
*
|
|
1984
|
+
* **Details**
|
|
1985
|
+
*
|
|
1986
|
+
* Keeps nodes from both graphs. Overlapping nodes use data from `that`. The
|
|
1987
|
+
* result has the same graph kind as `self`. Throws a `GraphError` when the
|
|
1988
|
+
* graph kinds do not match. `nodeIdentity` and `edgeIdentity` default to the
|
|
1989
|
+
* complete node and edge data. Edge identity also includes the endpoint
|
|
1990
|
+
* identities.
|
|
1991
|
+
*
|
|
1992
|
+
* `G1 Δ G2 = {V1 ∪ V2, (E1 ∪ E2) \ (E1 ∩ E2)}`
|
|
1993
|
+
*
|
|
1994
|
+
* **Gotchas**
|
|
1995
|
+
*
|
|
1996
|
+
* Edges with different projected identities are distinct.
|
|
1997
|
+
* Nodes with equal identities in one input graph are coalesced. The last node
|
|
1998
|
+
* supplies the data, and redirected edges can collapse or become self-loops.
|
|
1999
|
+
* Parallel edges with equal identities are coalesced before the graphs are
|
|
2000
|
+
* compared.
|
|
2001
|
+
*
|
|
2002
|
+
* **Example** (Finding differing edges)
|
|
2003
|
+
*
|
|
2004
|
+
* ```ts
|
|
2005
|
+
* import { Graph } from "effect"
|
|
2006
|
+
*
|
|
2007
|
+
* const left = Graph.directed<string, string>((mutable) => {
|
|
2008
|
+
* const a = Graph.addNode(mutable, "A")
|
|
2009
|
+
* const b = Graph.addNode(mutable, "B")
|
|
2010
|
+
* const c = Graph.addNode(mutable, "C")
|
|
2011
|
+
* Graph.addEdge(mutable, a, b, "A-B")
|
|
2012
|
+
* Graph.addEdge(mutable, b, c, "B-C")
|
|
2013
|
+
* })
|
|
2014
|
+
*
|
|
2015
|
+
* const right = Graph.directed<string, string>((mutable) => {
|
|
2016
|
+
* const b = Graph.addNode(mutable, "B")
|
|
2017
|
+
* const c = Graph.addNode(mutable, "C")
|
|
2018
|
+
* const d = Graph.addNode(mutable, "D")
|
|
2019
|
+
* Graph.addEdge(mutable, b, c, "B-C")
|
|
2020
|
+
* Graph.addEdge(mutable, c, d, "C-D")
|
|
2021
|
+
* })
|
|
2022
|
+
*
|
|
2023
|
+
* const result = Graph.symmetricDifference(left, right)
|
|
2024
|
+
*
|
|
2025
|
+
* console.log(Graph.nodeCount(result)) // 4
|
|
2026
|
+
* console.log(Graph.edgeCount(result)) // 2
|
|
2027
|
+
* ```
|
|
2028
|
+
*
|
|
2029
|
+
* @category set operations
|
|
2030
|
+
* @since 4.0.0
|
|
2031
|
+
*/
|
|
2032
|
+
export const symmetricDifference: {
|
|
2033
|
+
/**
|
|
2034
|
+
* Returns edges present in exactly one of two graphs.
|
|
2035
|
+
*
|
|
2036
|
+
* **Details**
|
|
2037
|
+
*
|
|
2038
|
+
* Keeps nodes from both graphs. Overlapping nodes use data from `that`. The
|
|
2039
|
+
* result has the same graph kind as `self`. Throws a `GraphError` when the
|
|
2040
|
+
* graph kinds do not match. `nodeIdentity` and `edgeIdentity` default to the
|
|
2041
|
+
* complete node and edge data. Edge identity also includes the endpoint
|
|
2042
|
+
* identities.
|
|
2043
|
+
*
|
|
2044
|
+
* `G1 Δ G2 = {V1 ∪ V2, (E1 ∪ E2) \ (E1 ∩ E2)}`
|
|
2045
|
+
*
|
|
2046
|
+
* **Gotchas**
|
|
2047
|
+
*
|
|
2048
|
+
* Edges with different projected identities are distinct.
|
|
2049
|
+
* Nodes with equal identities in one input graph are coalesced. The last node
|
|
2050
|
+
* supplies the data, and redirected edges can collapse or become self-loops.
|
|
2051
|
+
* Parallel edges with equal identities are coalesced before the graphs are
|
|
2052
|
+
* compared.
|
|
2053
|
+
*
|
|
2054
|
+
* **Example** (Finding differing edges)
|
|
2055
|
+
*
|
|
2056
|
+
* ```ts
|
|
2057
|
+
* import { Graph } from "effect"
|
|
2058
|
+
*
|
|
2059
|
+
* const left = Graph.directed<string, string>((mutable) => {
|
|
2060
|
+
* const a = Graph.addNode(mutable, "A")
|
|
2061
|
+
* const b = Graph.addNode(mutable, "B")
|
|
2062
|
+
* const c = Graph.addNode(mutable, "C")
|
|
2063
|
+
* Graph.addEdge(mutable, a, b, "A-B")
|
|
2064
|
+
* Graph.addEdge(mutable, b, c, "B-C")
|
|
2065
|
+
* })
|
|
2066
|
+
*
|
|
2067
|
+
* const right = Graph.directed<string, string>((mutable) => {
|
|
2068
|
+
* const b = Graph.addNode(mutable, "B")
|
|
2069
|
+
* const c = Graph.addNode(mutable, "C")
|
|
2070
|
+
* const d = Graph.addNode(mutable, "D")
|
|
2071
|
+
* Graph.addEdge(mutable, b, c, "B-C")
|
|
2072
|
+
* Graph.addEdge(mutable, c, d, "C-D")
|
|
2073
|
+
* })
|
|
2074
|
+
*
|
|
2075
|
+
* const result = Graph.symmetricDifference(left, right)
|
|
2076
|
+
*
|
|
2077
|
+
* console.log(Graph.nodeCount(result)) // 4
|
|
2078
|
+
* console.log(Graph.edgeCount(result)) // 2
|
|
2079
|
+
* ```
|
|
2080
|
+
*
|
|
2081
|
+
* @category set operations
|
|
2082
|
+
* @since 4.0.0
|
|
2083
|
+
*/
|
|
2084
|
+
<N, E, T extends Kind = "directed", NI = N, EI = E>(that: Graph<N, E, T>, options?: IdentityOptions<N, E, NI, EI>): (self: Graph<N, E, NoInfer<T>>) => Graph<N, E, T>
|
|
2085
|
+
/**
|
|
2086
|
+
* Returns edges present in exactly one of two graphs.
|
|
2087
|
+
*
|
|
2088
|
+
* **Details**
|
|
2089
|
+
*
|
|
2090
|
+
* Keeps nodes from both graphs. Overlapping nodes use data from `that`. The
|
|
2091
|
+
* result has the same graph kind as `self`. Throws a `GraphError` when the
|
|
2092
|
+
* graph kinds do not match. `nodeIdentity` and `edgeIdentity` default to the
|
|
2093
|
+
* complete node and edge data. Edge identity also includes the endpoint
|
|
2094
|
+
* identities.
|
|
2095
|
+
*
|
|
2096
|
+
* `G1 Δ G2 = {V1 ∪ V2, (E1 ∪ E2) \ (E1 ∩ E2)}`
|
|
2097
|
+
*
|
|
2098
|
+
* **Gotchas**
|
|
2099
|
+
*
|
|
2100
|
+
* Edges with different projected identities are distinct.
|
|
2101
|
+
* Nodes with equal identities in one input graph are coalesced. The last node
|
|
2102
|
+
* supplies the data, and redirected edges can collapse or become self-loops.
|
|
2103
|
+
* Parallel edges with equal identities are coalesced before the graphs are
|
|
2104
|
+
* compared.
|
|
2105
|
+
*
|
|
2106
|
+
* **Example** (Finding differing edges)
|
|
2107
|
+
*
|
|
2108
|
+
* ```ts
|
|
2109
|
+
* import { Graph } from "effect"
|
|
2110
|
+
*
|
|
2111
|
+
* const left = Graph.directed<string, string>((mutable) => {
|
|
2112
|
+
* const a = Graph.addNode(mutable, "A")
|
|
2113
|
+
* const b = Graph.addNode(mutable, "B")
|
|
2114
|
+
* const c = Graph.addNode(mutable, "C")
|
|
2115
|
+
* Graph.addEdge(mutable, a, b, "A-B")
|
|
2116
|
+
* Graph.addEdge(mutable, b, c, "B-C")
|
|
2117
|
+
* })
|
|
2118
|
+
*
|
|
2119
|
+
* const right = Graph.directed<string, string>((mutable) => {
|
|
2120
|
+
* const b = Graph.addNode(mutable, "B")
|
|
2121
|
+
* const c = Graph.addNode(mutable, "C")
|
|
2122
|
+
* const d = Graph.addNode(mutable, "D")
|
|
2123
|
+
* Graph.addEdge(mutable, b, c, "B-C")
|
|
2124
|
+
* Graph.addEdge(mutable, c, d, "C-D")
|
|
2125
|
+
* })
|
|
2126
|
+
*
|
|
2127
|
+
* const result = Graph.symmetricDifference(left, right)
|
|
2128
|
+
*
|
|
2129
|
+
* console.log(Graph.nodeCount(result)) // 4
|
|
2130
|
+
* console.log(Graph.edgeCount(result)) // 2
|
|
2131
|
+
* ```
|
|
2132
|
+
*
|
|
2133
|
+
* @category set operations
|
|
2134
|
+
* @since 4.0.0
|
|
2135
|
+
*/
|
|
2136
|
+
<N, E, T extends Kind = "directed", NI = N, EI = E>(
|
|
2137
|
+
self: Graph<N, E, T>,
|
|
2138
|
+
that: Graph<N, E, NoInfer<T>>,
|
|
2139
|
+
options?: IdentityOptions<N, E, NI, EI>
|
|
2140
|
+
): Graph<N, E, T>
|
|
2141
|
+
} = dual((args) => isGraph(args[0]) && isGraph(args[1]), <N, E, T extends Kind, NI, EI>(
|
|
2142
|
+
self: Graph<N, E, T>,
|
|
2143
|
+
that: Graph<N, E, T>,
|
|
2144
|
+
options?: IdentityOptions<N, E, NI, EI>
|
|
2145
|
+
): Graph<N, E, T> => {
|
|
2146
|
+
assertSameKind(self, that)
|
|
2147
|
+
const getNodeIdentity = options?.nodeIdentity ?? ((node: N) => node as unknown as NI)
|
|
2148
|
+
const getEdgeIdentity = options?.edgeIdentity ?? ((edge: E) => edge as unknown as EI)
|
|
2149
|
+
const selfMaps = buildNodeMaps(self, getNodeIdentity)
|
|
2150
|
+
const thatMaps = buildNodeMaps(that, getNodeIdentity)
|
|
2151
|
+
const nodes = MutableHashMap.empty</**
|
|
2152
|
+
* Returns edges present in exactly one of two graphs.
|
|
2153
|
+
*
|
|
2154
|
+
* **Details**
|
|
2155
|
+
*
|
|
2156
|
+
* Keeps nodes from both graphs. Overlapping nodes use data from `that`. The
|
|
2157
|
+
* result has the same graph kind as `self`. Throws a `GraphError` when the
|
|
2158
|
+
* graph kinds do not match. `nodeIdentity` and `edgeIdentity` default to the
|
|
2159
|
+
* complete node and edge data. Edge identity also includes the endpoint
|
|
2160
|
+
* identities.
|
|
2161
|
+
*
|
|
2162
|
+
* `G1 Δ G2 = {V1 ∪ V2, (E1 ∪ E2) \ (E1 ∩ E2)}`
|
|
2163
|
+
*
|
|
2164
|
+
* **Gotchas**
|
|
2165
|
+
*
|
|
2166
|
+
* Edges with different projected identities are distinct.
|
|
2167
|
+
* Nodes with equal identities in one input graph are coalesced. The last node
|
|
2168
|
+
* supplies the data, and redirected edges can collapse or become self-loops.
|
|
2169
|
+
* Parallel edges with equal identities are coalesced before the graphs are
|
|
2170
|
+
* compared.
|
|
2171
|
+
*
|
|
2172
|
+
* **Example** (Finding differing edges)
|
|
2173
|
+
*
|
|
2174
|
+
* ```ts
|
|
2175
|
+
* import { Graph } from "effect"
|
|
2176
|
+
*
|
|
2177
|
+
* const left = Graph.directed<string, string>((mutable) => {
|
|
2178
|
+
* const a = Graph.addNode(mutable, "A")
|
|
2179
|
+
* const b = Graph.addNode(mutable, "B")
|
|
2180
|
+
* const c = Graph.addNode(mutable, "C")
|
|
2181
|
+
* Graph.addEdge(mutable, a, b, "A-B")
|
|
2182
|
+
* Graph.addEdge(mutable, b, c, "B-C")
|
|
2183
|
+
* })
|
|
2184
|
+
*
|
|
2185
|
+
* const right = Graph.directed<string, string>((mutable) => {
|
|
2186
|
+
* const b = Graph.addNode(mutable, "B")
|
|
2187
|
+
* const c = Graph.addNode(mutable, "C")
|
|
2188
|
+
* const d = Graph.addNode(mutable, "D")
|
|
2189
|
+
* Graph.addEdge(mutable, b, c, "B-C")
|
|
2190
|
+
* Graph.addEdge(mutable, c, d, "C-D")
|
|
2191
|
+
* })
|
|
2192
|
+
*
|
|
2193
|
+
* const result = Graph.symmetricDifference(left, right)
|
|
2194
|
+
*
|
|
2195
|
+
* console.log(Graph.nodeCount(result)) // 4
|
|
2196
|
+
* console.log(Graph.edgeCount(result)) // 2
|
|
2197
|
+
* ```
|
|
2198
|
+
*
|
|
2199
|
+
* @category set operations
|
|
2200
|
+
* @since 4.0.0
|
|
2201
|
+
*/
|
|
2202
|
+
NI, /**
|
|
2203
|
+
* Returns edges present in exactly one of two graphs.
|
|
2204
|
+
*
|
|
2205
|
+
* **Details**
|
|
2206
|
+
*
|
|
2207
|
+
* Keeps nodes from both graphs. Overlapping nodes use data from `that`. The
|
|
2208
|
+
* result has the same graph kind as `self`. Throws a `GraphError` when the
|
|
2209
|
+
* graph kinds do not match. `nodeIdentity` and `edgeIdentity` default to the
|
|
2210
|
+
* complete node and edge data. Edge identity also includes the endpoint
|
|
2211
|
+
* identities.
|
|
2212
|
+
*
|
|
2213
|
+
* `G1 Δ G2 = {V1 ∪ V2, (E1 ∪ E2) \ (E1 ∩ E2)}`
|
|
2214
|
+
*
|
|
2215
|
+
* **Gotchas**
|
|
2216
|
+
*
|
|
2217
|
+
* Edges with different projected identities are distinct.
|
|
2218
|
+
* Nodes with equal identities in one input graph are coalesced. The last node
|
|
2219
|
+
* supplies the data, and redirected edges can collapse or become self-loops.
|
|
2220
|
+
* Parallel edges with equal identities are coalesced before the graphs are
|
|
2221
|
+
* compared.
|
|
2222
|
+
*
|
|
2223
|
+
* **Example** (Finding differing edges)
|
|
2224
|
+
*
|
|
2225
|
+
* ```ts
|
|
2226
|
+
* import { Graph } from "effect"
|
|
2227
|
+
*
|
|
2228
|
+
* const left = Graph.directed<string, string>((mutable) => {
|
|
2229
|
+
* const a = Graph.addNode(mutable, "A")
|
|
2230
|
+
* const b = Graph.addNode(mutable, "B")
|
|
2231
|
+
* const c = Graph.addNode(mutable, "C")
|
|
2232
|
+
* Graph.addEdge(mutable, a, b, "A-B")
|
|
2233
|
+
* Graph.addEdge(mutable, b, c, "B-C")
|
|
2234
|
+
* })
|
|
2235
|
+
*
|
|
2236
|
+
* const right = Graph.directed<string, string>((mutable) => {
|
|
2237
|
+
* const b = Graph.addNode(mutable, "B")
|
|
2238
|
+
* const c = Graph.addNode(mutable, "C")
|
|
2239
|
+
* const d = Graph.addNode(mutable, "D")
|
|
2240
|
+
* Graph.addEdge(mutable, b, c, "B-C")
|
|
2241
|
+
* Graph.addEdge(mutable, c, d, "C-D")
|
|
2242
|
+
* })
|
|
2243
|
+
*
|
|
2244
|
+
* const result = Graph.symmetricDifference(left, right)
|
|
2245
|
+
*
|
|
2246
|
+
* console.log(Graph.nodeCount(result)) // 4
|
|
2247
|
+
* console.log(Graph.edgeCount(result)) // 2
|
|
2248
|
+
* ```
|
|
2249
|
+
*
|
|
2250
|
+
* @category set operations
|
|
2251
|
+
* @since 4.0.0
|
|
2252
|
+
*/
|
|
2253
|
+
N>()
|
|
2254
|
+
const selfEdges = buildEdgeMap(self, selfMaps, getEdgeIdentity)
|
|
2255
|
+
const thatEdges = buildEdgeMap(that, thatMaps, getEdgeIdentity)
|
|
2256
|
+
|
|
2257
|
+
for (const [identity, data] of selfMaps.byIdentity) {
|
|
2258
|
+
MutableHashMap.set(nodes, identity, data)
|
|
2259
|
+
}
|
|
2260
|
+
|
|
2261
|
+
for (const [identity, data] of thatMaps.byIdentity) {
|
|
2262
|
+
MutableHashMap.set(nodes, identity, data)
|
|
2263
|
+
}
|
|
2264
|
+
|
|
2265
|
+
return make(self.type)</**
|
|
2266
|
+
* Returns edges present in exactly one of two graphs.
|
|
2267
|
+
*
|
|
2268
|
+
* **Details**
|
|
2269
|
+
*
|
|
2270
|
+
* Keeps nodes from both graphs. Overlapping nodes use data from `that`. The
|
|
2271
|
+
* result has the same graph kind as `self`. Throws a `GraphError` when the
|
|
2272
|
+
* graph kinds do not match. `nodeIdentity` and `edgeIdentity` default to the
|
|
2273
|
+
* complete node and edge data. Edge identity also includes the endpoint
|
|
2274
|
+
* identities.
|
|
2275
|
+
*
|
|
2276
|
+
* `G1 Δ G2 = {V1 ∪ V2, (E1 ∪ E2) \ (E1 ∩ E2)}`
|
|
2277
|
+
*
|
|
2278
|
+
* **Gotchas**
|
|
2279
|
+
*
|
|
2280
|
+
* Edges with different projected identities are distinct.
|
|
2281
|
+
* Nodes with equal identities in one input graph are coalesced. The last node
|
|
2282
|
+
* supplies the data, and redirected edges can collapse or become self-loops.
|
|
2283
|
+
* Parallel edges with equal identities are coalesced before the graphs are
|
|
2284
|
+
* compared.
|
|
2285
|
+
*
|
|
2286
|
+
* **Example** (Finding differing edges)
|
|
2287
|
+
*
|
|
2288
|
+
* ```ts
|
|
2289
|
+
* import { Graph } from "effect"
|
|
2290
|
+
*
|
|
2291
|
+
* const left = Graph.directed<string, string>((mutable) => {
|
|
2292
|
+
* const a = Graph.addNode(mutable, "A")
|
|
2293
|
+
* const b = Graph.addNode(mutable, "B")
|
|
2294
|
+
* const c = Graph.addNode(mutable, "C")
|
|
2295
|
+
* Graph.addEdge(mutable, a, b, "A-B")
|
|
2296
|
+
* Graph.addEdge(mutable, b, c, "B-C")
|
|
2297
|
+
* })
|
|
2298
|
+
*
|
|
2299
|
+
* const right = Graph.directed<string, string>((mutable) => {
|
|
2300
|
+
* const b = Graph.addNode(mutable, "B")
|
|
2301
|
+
* const c = Graph.addNode(mutable, "C")
|
|
2302
|
+
* const d = Graph.addNode(mutable, "D")
|
|
2303
|
+
* Graph.addEdge(mutable, b, c, "B-C")
|
|
2304
|
+
* Graph.addEdge(mutable, c, d, "C-D")
|
|
2305
|
+
* })
|
|
2306
|
+
*
|
|
2307
|
+
* const result = Graph.symmetricDifference(left, right)
|
|
2308
|
+
*
|
|
2309
|
+
* console.log(Graph.nodeCount(result)) // 4
|
|
2310
|
+
* console.log(Graph.edgeCount(result)) // 2
|
|
2311
|
+
* ```
|
|
2312
|
+
*
|
|
2313
|
+
* @category set operations
|
|
2314
|
+
* @since 4.0.0
|
|
2315
|
+
*/
|
|
2316
|
+
N, /**
|
|
2317
|
+
* Returns edges present in exactly one of two graphs.
|
|
2318
|
+
*
|
|
2319
|
+
* **Details**
|
|
2320
|
+
*
|
|
2321
|
+
* Keeps nodes from both graphs. Overlapping nodes use data from `that`. The
|
|
2322
|
+
* result has the same graph kind as `self`. Throws a `GraphError` when the
|
|
2323
|
+
* graph kinds do not match. `nodeIdentity` and `edgeIdentity` default to the
|
|
2324
|
+
* complete node and edge data. Edge identity also includes the endpoint
|
|
2325
|
+
* identities.
|
|
2326
|
+
*
|
|
2327
|
+
* `G1 Δ G2 = {V1 ∪ V2, (E1 ∪ E2) \ (E1 ∩ E2)}`
|
|
2328
|
+
*
|
|
2329
|
+
* **Gotchas**
|
|
2330
|
+
*
|
|
2331
|
+
* Edges with different projected identities are distinct.
|
|
2332
|
+
* Nodes with equal identities in one input graph are coalesced. The last node
|
|
2333
|
+
* supplies the data, and redirected edges can collapse or become self-loops.
|
|
2334
|
+
* Parallel edges with equal identities are coalesced before the graphs are
|
|
2335
|
+
* compared.
|
|
2336
|
+
*
|
|
2337
|
+
* **Example** (Finding differing edges)
|
|
2338
|
+
*
|
|
2339
|
+
* ```ts
|
|
2340
|
+
* import { Graph } from "effect"
|
|
2341
|
+
*
|
|
2342
|
+
* const left = Graph.directed<string, string>((mutable) => {
|
|
2343
|
+
* const a = Graph.addNode(mutable, "A")
|
|
2344
|
+
* const b = Graph.addNode(mutable, "B")
|
|
2345
|
+
* const c = Graph.addNode(mutable, "C")
|
|
2346
|
+
* Graph.addEdge(mutable, a, b, "A-B")
|
|
2347
|
+
* Graph.addEdge(mutable, b, c, "B-C")
|
|
2348
|
+
* })
|
|
2349
|
+
*
|
|
2350
|
+
* const right = Graph.directed<string, string>((mutable) => {
|
|
2351
|
+
* const b = Graph.addNode(mutable, "B")
|
|
2352
|
+
* const c = Graph.addNode(mutable, "C")
|
|
2353
|
+
* const d = Graph.addNode(mutable, "D")
|
|
2354
|
+
* Graph.addEdge(mutable, b, c, "B-C")
|
|
2355
|
+
* Graph.addEdge(mutable, c, d, "C-D")
|
|
2356
|
+
* })
|
|
2357
|
+
*
|
|
2358
|
+
* const result = Graph.symmetricDifference(left, right)
|
|
2359
|
+
*
|
|
2360
|
+
* console.log(Graph.nodeCount(result)) // 4
|
|
2361
|
+
* console.log(Graph.edgeCount(result)) // 2
|
|
2362
|
+
* ```
|
|
2363
|
+
*
|
|
2364
|
+
* @category set operations
|
|
2365
|
+
* @since 4.0.0
|
|
2366
|
+
*/
|
|
2367
|
+
E>((mutable) => {
|
|
2368
|
+
const indexByIdentity = addNodesByIdentity(mutable, nodes)
|
|
2369
|
+
|
|
2370
|
+
for (const [identity, data] of selfEdges) {
|
|
2371
|
+
if (!MutableHashMap.has(thatEdges, identity)) {
|
|
2372
|
+
addEdgeByIdentity(mutable, indexByIdentity, identity, data)
|
|
2373
|
+
}
|
|
2374
|
+
}
|
|
2375
|
+
|
|
2376
|
+
for (const [identity, data] of thatEdges) {
|
|
2377
|
+
if (!MutableHashMap.has(selfEdges, identity)) {
|
|
2378
|
+
addEdgeByIdentity(mutable, indexByIdentity, identity, data)
|
|
2379
|
+
}
|
|
2380
|
+
}
|
|
2381
|
+
});
|
|
2382
|
+
})
|
|
2383
|
+
|
|
2384
|
+
/**
|
|
2385
|
+
* Returns the complement over the existing node set.
|
|
2386
|
+
*
|
|
2387
|
+
* **Details**
|
|
2388
|
+
*
|
|
2389
|
+
* Adds every missing edge between distinct nodes. The `createEdge` function
|
|
2390
|
+
* receives the source and target node data for each added edge. The result has
|
|
2391
|
+
* the same graph kind as `self`.
|
|
2392
|
+
*
|
|
2393
|
+
* `G' = {V, (V x V) \ E}`
|
|
2394
|
+
*
|
|
2395
|
+
* **Example** (Finding missing relationships)
|
|
2396
|
+
*
|
|
2397
|
+
* ```ts
|
|
2398
|
+
* import { Graph } from "effect"
|
|
2399
|
+
*
|
|
2400
|
+
* const graph = Graph.directed<string, string>((mutable) => {
|
|
2401
|
+
* const a = Graph.addNode(mutable, "A")
|
|
2402
|
+
* const b = Graph.addNode(mutable, "B")
|
|
2403
|
+
* Graph.addEdge(mutable, a, b, "A-B")
|
|
2404
|
+
* })
|
|
2405
|
+
*
|
|
2406
|
+
* const result = Graph.complement(graph, (source, target) => `${source}-${target}`)
|
|
2407
|
+
*
|
|
2408
|
+
* console.log(Graph.edgeCount(result)) // 1
|
|
2409
|
+
* ```
|
|
2410
|
+
*
|
|
2411
|
+
* @category set operations
|
|
2412
|
+
* @since 4.0.0
|
|
2413
|
+
*/
|
|
2414
|
+
export const complement: {
|
|
2415
|
+
/**
|
|
2416
|
+
* Returns the complement over the existing node set.
|
|
2417
|
+
*
|
|
2418
|
+
* **Details**
|
|
2419
|
+
*
|
|
2420
|
+
* Adds every missing edge between distinct nodes. The `createEdge` function
|
|
2421
|
+
* receives the source and target node data for each added edge. The result has
|
|
2422
|
+
* the same graph kind as `self`.
|
|
2423
|
+
*
|
|
2424
|
+
* `G' = {V, (V x V) \ E}`
|
|
2425
|
+
*
|
|
2426
|
+
* **Example** (Finding missing relationships)
|
|
2427
|
+
*
|
|
2428
|
+
* ```ts
|
|
2429
|
+
* import { Graph } from "effect"
|
|
2430
|
+
*
|
|
2431
|
+
* const graph = Graph.directed<string, string>((mutable) => {
|
|
2432
|
+
* const a = Graph.addNode(mutable, "A")
|
|
2433
|
+
* const b = Graph.addNode(mutable, "B")
|
|
2434
|
+
* Graph.addEdge(mutable, a, b, "A-B")
|
|
2435
|
+
* })
|
|
2436
|
+
*
|
|
2437
|
+
* const result = Graph.complement(graph, (source, target) => `${source}-${target}`)
|
|
2438
|
+
*
|
|
2439
|
+
* console.log(Graph.edgeCount(result)) // 1
|
|
2440
|
+
* ```
|
|
2441
|
+
*
|
|
2442
|
+
* @category set operations
|
|
2443
|
+
* @since 4.0.0
|
|
2444
|
+
*/
|
|
2445
|
+
<N, E>(createEdge: (source: N, target: N) => E): <T extends Kind = "directed">(self: Graph<N, E, T>) => Graph<N, E, T>
|
|
2446
|
+
/**
|
|
2447
|
+
* Returns the complement over the existing node set.
|
|
2448
|
+
*
|
|
2449
|
+
* **Details**
|
|
2450
|
+
*
|
|
2451
|
+
* Adds every missing edge between distinct nodes. The `createEdge` function
|
|
2452
|
+
* receives the source and target node data for each added edge. The result has
|
|
2453
|
+
* the same graph kind as `self`.
|
|
2454
|
+
*
|
|
2455
|
+
* `G' = {V, (V x V) \ E}`
|
|
2456
|
+
*
|
|
2457
|
+
* **Example** (Finding missing relationships)
|
|
2458
|
+
*
|
|
2459
|
+
* ```ts
|
|
2460
|
+
* import { Graph } from "effect"
|
|
2461
|
+
*
|
|
2462
|
+
* const graph = Graph.directed<string, string>((mutable) => {
|
|
2463
|
+
* const a = Graph.addNode(mutable, "A")
|
|
2464
|
+
* const b = Graph.addNode(mutable, "B")
|
|
2465
|
+
* Graph.addEdge(mutable, a, b, "A-B")
|
|
2466
|
+
* })
|
|
2467
|
+
*
|
|
2468
|
+
* const result = Graph.complement(graph, (source, target) => `${source}-${target}`)
|
|
2469
|
+
*
|
|
2470
|
+
* console.log(Graph.edgeCount(result)) // 1
|
|
2471
|
+
* ```
|
|
2472
|
+
*
|
|
2473
|
+
* @category set operations
|
|
2474
|
+
* @since 4.0.0
|
|
2475
|
+
*/
|
|
2476
|
+
<N, E, T extends Kind = "directed">(self: Graph<N, E, T>, createEdge: (source: N, target: N) => E): Graph<N, E, T>
|
|
2477
|
+
} = dual(2, <N, E, T extends Kind>(
|
|
2478
|
+
self: Graph<N, E, T>,
|
|
2479
|
+
createEdge: (source: N, target: N) => E
|
|
2480
|
+
): Graph<N, E, T> => {
|
|
2481
|
+
const selfImpl = graphImpl(self)
|
|
2482
|
+
const nodeEntries = Array.from(selfImpl.nodes)
|
|
2483
|
+
|
|
2484
|
+
return make(self.type)</**
|
|
2485
|
+
* Returns the complement over the existing node set.
|
|
2486
|
+
*
|
|
2487
|
+
* **Details**
|
|
2488
|
+
*
|
|
2489
|
+
* Adds every missing edge between distinct nodes. The `createEdge` function
|
|
2490
|
+
* receives the source and target node data for each added edge. The result has
|
|
2491
|
+
* the same graph kind as `self`.
|
|
2492
|
+
*
|
|
2493
|
+
* `G' = {V, (V x V) \ E}`
|
|
2494
|
+
*
|
|
2495
|
+
* **Example** (Finding missing relationships)
|
|
2496
|
+
*
|
|
2497
|
+
* ```ts
|
|
2498
|
+
* import { Graph } from "effect"
|
|
2499
|
+
*
|
|
2500
|
+
* const graph = Graph.directed<string, string>((mutable) => {
|
|
2501
|
+
* const a = Graph.addNode(mutable, "A")
|
|
2502
|
+
* const b = Graph.addNode(mutable, "B")
|
|
2503
|
+
* Graph.addEdge(mutable, a, b, "A-B")
|
|
2504
|
+
* })
|
|
2505
|
+
*
|
|
2506
|
+
* const result = Graph.complement(graph, (source, target) => `${source}-${target}`)
|
|
2507
|
+
*
|
|
2508
|
+
* console.log(Graph.edgeCount(result)) // 1
|
|
2509
|
+
* ```
|
|
2510
|
+
*
|
|
2511
|
+
* @category set operations
|
|
2512
|
+
* @since 4.0.0
|
|
2513
|
+
*/
|
|
2514
|
+
N, /**
|
|
2515
|
+
* Returns the complement over the existing node set.
|
|
2516
|
+
*
|
|
2517
|
+
* **Details**
|
|
2518
|
+
*
|
|
2519
|
+
* Adds every missing edge between distinct nodes. The `createEdge` function
|
|
2520
|
+
* receives the source and target node data for each added edge. The result has
|
|
2521
|
+
* the same graph kind as `self`.
|
|
2522
|
+
*
|
|
2523
|
+
* `G' = {V, (V x V) \ E}`
|
|
2524
|
+
*
|
|
2525
|
+
* **Example** (Finding missing relationships)
|
|
2526
|
+
*
|
|
2527
|
+
* ```ts
|
|
2528
|
+
* import { Graph } from "effect"
|
|
2529
|
+
*
|
|
2530
|
+
* const graph = Graph.directed<string, string>((mutable) => {
|
|
2531
|
+
* const a = Graph.addNode(mutable, "A")
|
|
2532
|
+
* const b = Graph.addNode(mutable, "B")
|
|
2533
|
+
* Graph.addEdge(mutable, a, b, "A-B")
|
|
2534
|
+
* })
|
|
2535
|
+
*
|
|
2536
|
+
* const result = Graph.complement(graph, (source, target) => `${source}-${target}`)
|
|
2537
|
+
*
|
|
2538
|
+
* console.log(Graph.edgeCount(result)) // 1
|
|
2539
|
+
* ```
|
|
2540
|
+
*
|
|
2541
|
+
* @category set operations
|
|
2542
|
+
* @since 4.0.0
|
|
2543
|
+
*/
|
|
2544
|
+
E>((mutable) => {
|
|
2545
|
+
const newIndexMap = new Map<NodeIndex, NodeIndex>()
|
|
2546
|
+
|
|
2547
|
+
for (const [oldIndex, data] of nodeEntries) {
|
|
2548
|
+
newIndexMap.set(oldIndex, addNode(mutable, data))
|
|
2549
|
+
}
|
|
2550
|
+
|
|
2551
|
+
for (let i = 0; i < nodeEntries.length; i++) {
|
|
2552
|
+
const [sourceOldIndex, sourceData] = nodeEntries[i]
|
|
2553
|
+
const start = self.type === "undirected" ? i + 1 : 0
|
|
2554
|
+
|
|
2555
|
+
for (let j = start; j < nodeEntries.length; j++) {
|
|
2556
|
+
const [targetOldIndex, targetData] = nodeEntries[j]
|
|
2557
|
+
if (sourceOldIndex === targetOldIndex || hasEdge(self, sourceOldIndex, targetOldIndex)) {
|
|
2558
|
+
continue
|
|
2559
|
+
}
|
|
2560
|
+
|
|
2561
|
+
const sourceIndex = newIndexMap.get(sourceOldIndex)
|
|
2562
|
+
const targetIndex = newIndexMap.get(targetOldIndex)
|
|
2563
|
+
if (sourceIndex !== undefined && targetIndex !== undefined) {
|
|
2564
|
+
addEdge(mutable, sourceIndex, targetIndex, createEdge(sourceData, targetData))
|
|
2565
|
+
}
|
|
2566
|
+
}
|
|
2567
|
+
}
|
|
2568
|
+
});
|
|
2569
|
+
})
|
|
2570
|
+
|
|
2571
|
+
/**
|
|
2572
|
+
* Configuration for selecting a graph neighborhood.
|
|
2573
|
+
*
|
|
2574
|
+
* **Details**
|
|
2575
|
+
*
|
|
2576
|
+
* `radius` limits the edge distance from the center node and defaults to `1`.
|
|
2577
|
+
* `direction` controls how directed edges are traversed and defaults to
|
|
2578
|
+
* `"outgoing"`.
|
|
2579
|
+
*
|
|
2580
|
+
* @category models
|
|
2581
|
+
* @since 4.0.0
|
|
2582
|
+
*/
|
|
2583
|
+
export interface NeighborhoodConfig {
|
|
2584
|
+
readonly radius?: number
|
|
2585
|
+
readonly direction?: TraversalDirection
|
|
2586
|
+
}
|
|
2587
|
+
|
|
2588
|
+
/**
|
|
2589
|
+
* Returns the induced subgraph containing nodes within a radius of a node.
|
|
2590
|
+
*
|
|
2591
|
+
* **Details**
|
|
2592
|
+
*
|
|
2593
|
+
* The `radius` option is the maximum edge distance from `nodeIndex` and
|
|
2594
|
+
* defaults to `1`. The `direction` option controls directed graph traversal and
|
|
2595
|
+
* defaults to `"outgoing"`. The result has the same graph kind as `self` and
|
|
2596
|
+
* keeps all original edges whose endpoints are both reached. `"undirected"`
|
|
2597
|
+
* ignores edge direction while finding reachable nodes.
|
|
2598
|
+
*
|
|
2599
|
+
* **Example** (Getting a local neighborhood)
|
|
2600
|
+
*
|
|
2601
|
+
* ```ts
|
|
2602
|
+
* import { Graph } from "effect"
|
|
2603
|
+
*
|
|
2604
|
+
* const graph = Graph.directed<string, string>((mutable) => {
|
|
2605
|
+
* const a = Graph.addNode(mutable, "A")
|
|
2606
|
+
* const b = Graph.addNode(mutable, "B")
|
|
2607
|
+
* const c = Graph.addNode(mutable, "C")
|
|
2608
|
+
* Graph.addEdge(mutable, a, b, "A-B")
|
|
2609
|
+
* Graph.addEdge(mutable, b, c, "B-C")
|
|
2610
|
+
* })
|
|
2611
|
+
*
|
|
2612
|
+
* const result = Graph.neighborhood(graph, 1, { radius: 1 })
|
|
2613
|
+
*
|
|
2614
|
+
* console.log(Graph.nodeCount(result)) // 2
|
|
2615
|
+
* ```
|
|
2616
|
+
*
|
|
2617
|
+
* @category set operations
|
|
2618
|
+
* @since 4.0.0
|
|
2619
|
+
*/
|
|
2620
|
+
export const neighborhood: {
|
|
2621
|
+
/**
|
|
2622
|
+
* Returns the induced subgraph containing nodes within a radius of a node.
|
|
2623
|
+
*
|
|
2624
|
+
* **Details**
|
|
2625
|
+
*
|
|
2626
|
+
* The `radius` option is the maximum edge distance from `nodeIndex` and
|
|
2627
|
+
* defaults to `1`. The `direction` option controls directed graph traversal and
|
|
2628
|
+
* defaults to `"outgoing"`. The result has the same graph kind as `self` and
|
|
2629
|
+
* keeps all original edges whose endpoints are both reached. `"undirected"`
|
|
2630
|
+
* ignores edge direction while finding reachable nodes.
|
|
2631
|
+
*
|
|
2632
|
+
* **Example** (Getting a local neighborhood)
|
|
2633
|
+
*
|
|
2634
|
+
* ```ts
|
|
2635
|
+
* import { Graph } from "effect"
|
|
2636
|
+
*
|
|
2637
|
+
* const graph = Graph.directed<string, string>((mutable) => {
|
|
2638
|
+
* const a = Graph.addNode(mutable, "A")
|
|
2639
|
+
* const b = Graph.addNode(mutable, "B")
|
|
2640
|
+
* const c = Graph.addNode(mutable, "C")
|
|
2641
|
+
* Graph.addEdge(mutable, a, b, "A-B")
|
|
2642
|
+
* Graph.addEdge(mutable, b, c, "B-C")
|
|
2643
|
+
* })
|
|
2644
|
+
*
|
|
2645
|
+
* const result = Graph.neighborhood(graph, 1, { radius: 1 })
|
|
2646
|
+
*
|
|
2647
|
+
* console.log(Graph.nodeCount(result)) // 2
|
|
2648
|
+
* ```
|
|
2649
|
+
*
|
|
2650
|
+
* @category set operations
|
|
2651
|
+
* @since 4.0.0
|
|
2652
|
+
*/
|
|
2653
|
+
(nodeIndex: NodeIndex, options?: NeighborhoodConfig): <N, E, T extends Kind = "directed">(self: Graph<N, E, T>) => Graph<N, E, T>
|
|
2654
|
+
/**
|
|
2655
|
+
* Returns the induced subgraph containing nodes within a radius of a node.
|
|
2656
|
+
*
|
|
2657
|
+
* **Details**
|
|
2658
|
+
*
|
|
2659
|
+
* The `radius` option is the maximum edge distance from `nodeIndex` and
|
|
2660
|
+
* defaults to `1`. The `direction` option controls directed graph traversal and
|
|
2661
|
+
* defaults to `"outgoing"`. The result has the same graph kind as `self` and
|
|
2662
|
+
* keeps all original edges whose endpoints are both reached. `"undirected"`
|
|
2663
|
+
* ignores edge direction while finding reachable nodes.
|
|
2664
|
+
*
|
|
2665
|
+
* **Example** (Getting a local neighborhood)
|
|
2666
|
+
*
|
|
2667
|
+
* ```ts
|
|
2668
|
+
* import { Graph } from "effect"
|
|
2669
|
+
*
|
|
2670
|
+
* const graph = Graph.directed<string, string>((mutable) => {
|
|
2671
|
+
* const a = Graph.addNode(mutable, "A")
|
|
2672
|
+
* const b = Graph.addNode(mutable, "B")
|
|
2673
|
+
* const c = Graph.addNode(mutable, "C")
|
|
2674
|
+
* Graph.addEdge(mutable, a, b, "A-B")
|
|
2675
|
+
* Graph.addEdge(mutable, b, c, "B-C")
|
|
2676
|
+
* })
|
|
2677
|
+
*
|
|
2678
|
+
* const result = Graph.neighborhood(graph, 1, { radius: 1 })
|
|
2679
|
+
*
|
|
2680
|
+
* console.log(Graph.nodeCount(result)) // 2
|
|
2681
|
+
* ```
|
|
2682
|
+
*
|
|
2683
|
+
* @category set operations
|
|
2684
|
+
* @since 4.0.0
|
|
2685
|
+
*/
|
|
2686
|
+
<N, E, T extends Kind = "directed">(self: Graph<N, E, T>, nodeIndex: NodeIndex, options?: NeighborhoodConfig): Graph<N, E, T>
|
|
2687
|
+
} = dual((args) => isGraph(args[0]), <N, E, T extends Kind>(
|
|
2688
|
+
self: Graph<N, E, T>,
|
|
2689
|
+
nodeIndex: NodeIndex,
|
|
2690
|
+
options?: NeighborhoodConfig
|
|
2691
|
+
): Graph<N, E, T> => {
|
|
2692
|
+
const selfImpl = graphImpl(self)
|
|
2693
|
+
const radius = options?.radius ?? 1
|
|
2694
|
+
const direction = options?.direction ?? "outgoing"
|
|
2695
|
+
const reached = new Set<NodeIndex>()
|
|
2696
|
+
|
|
2697
|
+
for (const index of indices(bfs(self, { start: [nodeIndex], direction, radius }))) {
|
|
2698
|
+
reached.add(index)
|
|
2699
|
+
}
|
|
2700
|
+
|
|
2701
|
+
return make(self.type)</**
|
|
2702
|
+
* Returns the induced subgraph containing nodes within a radius of a node.
|
|
2703
|
+
*
|
|
2704
|
+
* **Details**
|
|
2705
|
+
*
|
|
2706
|
+
* The `radius` option is the maximum edge distance from `nodeIndex` and
|
|
2707
|
+
* defaults to `1`. The `direction` option controls directed graph traversal and
|
|
2708
|
+
* defaults to `"outgoing"`. The result has the same graph kind as `self` and
|
|
2709
|
+
* keeps all original edges whose endpoints are both reached. `"undirected"`
|
|
2710
|
+
* ignores edge direction while finding reachable nodes.
|
|
2711
|
+
*
|
|
2712
|
+
* **Example** (Getting a local neighborhood)
|
|
2713
|
+
*
|
|
2714
|
+
* ```ts
|
|
2715
|
+
* import { Graph } from "effect"
|
|
2716
|
+
*
|
|
2717
|
+
* const graph = Graph.directed<string, string>((mutable) => {
|
|
2718
|
+
* const a = Graph.addNode(mutable, "A")
|
|
2719
|
+
* const b = Graph.addNode(mutable, "B")
|
|
2720
|
+
* const c = Graph.addNode(mutable, "C")
|
|
2721
|
+
* Graph.addEdge(mutable, a, b, "A-B")
|
|
2722
|
+
* Graph.addEdge(mutable, b, c, "B-C")
|
|
2723
|
+
* })
|
|
2724
|
+
*
|
|
2725
|
+
* const result = Graph.neighborhood(graph, 1, { radius: 1 })
|
|
2726
|
+
*
|
|
2727
|
+
* console.log(Graph.nodeCount(result)) // 2
|
|
2728
|
+
* ```
|
|
2729
|
+
*
|
|
2730
|
+
* @category set operations
|
|
2731
|
+
* @since 4.0.0
|
|
2732
|
+
*/
|
|
2733
|
+
N, /**
|
|
2734
|
+
* Returns the induced subgraph containing nodes within a radius of a node.
|
|
2735
|
+
*
|
|
2736
|
+
* **Details**
|
|
2737
|
+
*
|
|
2738
|
+
* The `radius` option is the maximum edge distance from `nodeIndex` and
|
|
2739
|
+
* defaults to `1`. The `direction` option controls directed graph traversal and
|
|
2740
|
+
* defaults to `"outgoing"`. The result has the same graph kind as `self` and
|
|
2741
|
+
* keeps all original edges whose endpoints are both reached. `"undirected"`
|
|
2742
|
+
* ignores edge direction while finding reachable nodes.
|
|
2743
|
+
*
|
|
2744
|
+
* **Example** (Getting a local neighborhood)
|
|
2745
|
+
*
|
|
2746
|
+
* ```ts
|
|
2747
|
+
* import { Graph } from "effect"
|
|
2748
|
+
*
|
|
2749
|
+
* const graph = Graph.directed<string, string>((mutable) => {
|
|
2750
|
+
* const a = Graph.addNode(mutable, "A")
|
|
2751
|
+
* const b = Graph.addNode(mutable, "B")
|
|
2752
|
+
* const c = Graph.addNode(mutable, "C")
|
|
2753
|
+
* Graph.addEdge(mutable, a, b, "A-B")
|
|
2754
|
+
* Graph.addEdge(mutable, b, c, "B-C")
|
|
2755
|
+
* })
|
|
2756
|
+
*
|
|
2757
|
+
* const result = Graph.neighborhood(graph, 1, { radius: 1 })
|
|
2758
|
+
*
|
|
2759
|
+
* console.log(Graph.nodeCount(result)) // 2
|
|
2760
|
+
* ```
|
|
2761
|
+
*
|
|
2762
|
+
* @category set operations
|
|
2763
|
+
* @since 4.0.0
|
|
2764
|
+
*/
|
|
2765
|
+
E>((mutable) => {
|
|
2766
|
+
const newIndexMap = new Map<NodeIndex, NodeIndex>()
|
|
538
2767
|
|
|
539
|
-
|
|
540
|
-
|
|
2768
|
+
for (const oldIndex of reached) {
|
|
2769
|
+
newIndexMap.set(oldIndex, addNode(mutable, Option.getOrThrow(getNode(self, oldIndex))))
|
|
2770
|
+
}
|
|
2771
|
+
|
|
2772
|
+
for (const edge of selfImpl.edges.values()) {
|
|
2773
|
+
if (reached.has(edge.source) && reached.has(edge.target)) {
|
|
2774
|
+
const sourceIndex = newIndexMap.get(edge.source)
|
|
2775
|
+
const targetIndex = newIndexMap.get(edge.target)
|
|
2776
|
+
if (sourceIndex !== undefined && targetIndex !== undefined) {
|
|
2777
|
+
addEdge(mutable, sourceIndex, targetIndex, edge.data)
|
|
2778
|
+
}
|
|
2779
|
+
}
|
|
2780
|
+
}
|
|
2781
|
+
});
|
|
2782
|
+
})
|
|
541
2783
|
|
|
542
2784
|
/**
|
|
543
|
-
*
|
|
2785
|
+
* Returns the disjoint union of two graphs.
|
|
544
2786
|
*
|
|
545
|
-
* **
|
|
546
|
-
*
|
|
547
|
-
* ```ts
|
|
548
|
-
* import { Graph } from "effect"
|
|
2787
|
+
* **Details**
|
|
549
2788
|
*
|
|
550
|
-
*
|
|
551
|
-
*
|
|
552
|
-
*
|
|
553
|
-
* const nodeB = Graph.addNode(mutable, "B")
|
|
554
|
-
* Graph.addEdge(mutable, nodeA, nodeB, 1)
|
|
555
|
-
* })
|
|
2789
|
+
* Copies all nodes and edges from both graphs without merging equal node data.
|
|
2790
|
+
* The result has the same graph kind as `self`. Throws a `GraphError` when the
|
|
2791
|
+
* graph kinds do not match.
|
|
556
2792
|
*
|
|
557
|
-
*
|
|
558
|
-
* console.log(Graph.edgeCount(newGraph)) // 1
|
|
559
|
-
* ```
|
|
2793
|
+
* `G1 + G2 = {disjoint V1 + V2, disjoint E1 + E2}`
|
|
560
2794
|
*
|
|
561
|
-
* @category
|
|
562
|
-
* @since
|
|
2795
|
+
* @category set operations
|
|
2796
|
+
* @since 4.0.0
|
|
563
2797
|
*/
|
|
564
|
-
export const
|
|
2798
|
+
export const sum: {
|
|
565
2799
|
/**
|
|
566
|
-
*
|
|
567
|
-
*
|
|
568
|
-
* **Example** (Applying scoped mutations)
|
|
2800
|
+
* Returns the disjoint union of two graphs.
|
|
569
2801
|
*
|
|
570
|
-
*
|
|
571
|
-
* import { Graph } from "effect"
|
|
2802
|
+
* **Details**
|
|
572
2803
|
*
|
|
573
|
-
*
|
|
574
|
-
*
|
|
575
|
-
*
|
|
576
|
-
* const nodeB = Graph.addNode(mutable, "B")
|
|
577
|
-
* Graph.addEdge(mutable, nodeA, nodeB, 1)
|
|
578
|
-
* })
|
|
2804
|
+
* Copies all nodes and edges from both graphs without merging equal node data.
|
|
2805
|
+
* The result has the same graph kind as `self`. Throws a `GraphError` when the
|
|
2806
|
+
* graph kinds do not match.
|
|
579
2807
|
*
|
|
580
|
-
*
|
|
581
|
-
* console.log(Graph.edgeCount(newGraph)) // 1
|
|
582
|
-
* ```
|
|
2808
|
+
* `G1 + G2 = {disjoint V1 + V2, disjoint E1 + E2}`
|
|
583
2809
|
*
|
|
584
|
-
* @category
|
|
585
|
-
* @since
|
|
2810
|
+
* @category set operations
|
|
2811
|
+
* @since 4.0.0
|
|
586
2812
|
*/
|
|
587
|
-
<N, E, T extends Kind
|
|
2813
|
+
<N, E, T extends Kind>(that: Graph<N, E, T>): (self: Graph<N, E, NoInfer<T>>) => Graph<N, E, T>
|
|
588
2814
|
/**
|
|
589
|
-
*
|
|
2815
|
+
* Returns the disjoint union of two graphs.
|
|
590
2816
|
*
|
|
591
|
-
* **
|
|
2817
|
+
* **Details**
|
|
592
2818
|
*
|
|
593
|
-
*
|
|
594
|
-
*
|
|
2819
|
+
* Copies all nodes and edges from both graphs without merging equal node data.
|
|
2820
|
+
* The result has the same graph kind as `self`. Throws a `GraphError` when the
|
|
2821
|
+
* graph kinds do not match.
|
|
595
2822
|
*
|
|
596
|
-
*
|
|
597
|
-
* const newGraph = Graph.mutate(graph, (mutable) => {
|
|
598
|
-
* const nodeA = Graph.addNode(mutable, "A")
|
|
599
|
-
* const nodeB = Graph.addNode(mutable, "B")
|
|
600
|
-
* Graph.addEdge(mutable, nodeA, nodeB, 1)
|
|
601
|
-
* })
|
|
2823
|
+
* `G1 + G2 = {disjoint V1 + V2, disjoint E1 + E2}`
|
|
602
2824
|
*
|
|
603
|
-
*
|
|
604
|
-
*
|
|
605
|
-
|
|
2825
|
+
* @category set operations
|
|
2826
|
+
* @since 4.0.0
|
|
2827
|
+
*/
|
|
2828
|
+
<N, E, T extends Kind>(self: Graph<N, E, T>, that: Graph<N, E, NoInfer<T>>): Graph<N, E, T>
|
|
2829
|
+
} = dual(2, <N, E, T extends Kind>(self: Graph<N, E, T>, that: Graph<N, E, T>): Graph<N, E, T> => {
|
|
2830
|
+
assertSameKind(self, that)
|
|
2831
|
+
return make(self.type)</**
|
|
2832
|
+
* Returns the disjoint union of two graphs.
|
|
606
2833
|
*
|
|
607
|
-
*
|
|
608
|
-
*
|
|
2834
|
+
* **Details**
|
|
2835
|
+
*
|
|
2836
|
+
* Copies all nodes and edges from both graphs without merging equal node data.
|
|
2837
|
+
* The result has the same graph kind as `self`. Throws a `GraphError` when the
|
|
2838
|
+
* graph kinds do not match.
|
|
2839
|
+
*
|
|
2840
|
+
* `G1 + G2 = {disjoint V1 + V2, disjoint E1 + E2}`
|
|
2841
|
+
*
|
|
2842
|
+
* @category set operations
|
|
2843
|
+
* @since 4.0.0
|
|
609
2844
|
*/
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
2845
|
+
N, /**
|
|
2846
|
+
* Returns the disjoint union of two graphs.
|
|
2847
|
+
*
|
|
2848
|
+
* **Details**
|
|
2849
|
+
*
|
|
2850
|
+
* Copies all nodes and edges from both graphs without merging equal node data.
|
|
2851
|
+
* The result has the same graph kind as `self`. Throws a `GraphError` when the
|
|
2852
|
+
* graph kinds do not match.
|
|
2853
|
+
*
|
|
2854
|
+
* `G1 + G2 = {disjoint V1 + V2, disjoint E1 + E2}`
|
|
2855
|
+
*
|
|
2856
|
+
* @category set operations
|
|
2857
|
+
* @since 4.0.0
|
|
2858
|
+
*/
|
|
2859
|
+
E>((mutable) => {
|
|
2860
|
+
const copyInto = (graph: Graph<N, E, T>) => {
|
|
2861
|
+
const impl = graphImpl(graph)
|
|
2862
|
+
const indexMap = new Map<NodeIndex, NodeIndex>()
|
|
2863
|
+
|
|
2864
|
+
for (const [oldIndex, data] of impl.nodes) {
|
|
2865
|
+
indexMap.set(oldIndex, addNode(mutable, data))
|
|
2866
|
+
}
|
|
2867
|
+
|
|
2868
|
+
for (const edge of impl.edges.values()) {
|
|
2869
|
+
const sourceIndex = indexMap.get(edge.source)
|
|
2870
|
+
const targetIndex = indexMap.get(edge.target)
|
|
2871
|
+
if (sourceIndex !== undefined && targetIndex !== undefined) {
|
|
2872
|
+
addEdge(mutable, sourceIndex, targetIndex, edge.data)
|
|
2873
|
+
}
|
|
2874
|
+
}
|
|
2875
|
+
}
|
|
2876
|
+
|
|
2877
|
+
copyInto(self)
|
|
2878
|
+
copyInto(that)
|
|
2879
|
+
});
|
|
618
2880
|
})
|
|
619
2881
|
|
|
620
2882
|
// =============================================================================
|
|
@@ -663,17 +2925,20 @@ export const addNode = <N, E, T extends Kind = "directed">(
|
|
|
663
2925
|
mutable: MutableGraph<N, E, T>,
|
|
664
2926
|
data: N
|
|
665
2927
|
): NodeIndex => {
|
|
666
|
-
|
|
2928
|
+
assertMutable(mutable)
|
|
2929
|
+
const impl = graphImpl(mutable)
|
|
2930
|
+
|
|
2931
|
+
const nodeIndex = impl.nextNodeIndex
|
|
667
2932
|
|
|
668
2933
|
// Add node data
|
|
669
|
-
|
|
2934
|
+
impl.nodes.set(nodeIndex, data)
|
|
670
2935
|
|
|
671
2936
|
// Initialize empty adjacency lists
|
|
672
|
-
|
|
673
|
-
|
|
2937
|
+
impl.adjacency.set(nodeIndex, [])
|
|
2938
|
+
impl.reverseAdjacency.set(nodeIndex, [])
|
|
674
2939
|
|
|
675
2940
|
// Update graph allocators
|
|
676
|
-
|
|
2941
|
+
impl.nextNodeIndex = impl.nextNodeIndex + 1
|
|
677
2942
|
|
|
678
2943
|
return nodeIndex
|
|
679
2944
|
}
|
|
@@ -725,7 +2990,9 @@ export const getNode: {
|
|
|
725
2990
|
* @category getters
|
|
726
2991
|
* @since 3.18.0
|
|
727
2992
|
*/
|
|
728
|
-
|
|
2993
|
+
(nodeIndex: NodeIndex): <N, E, T extends Kind = "directed">(
|
|
2994
|
+
graph: Graph<N, E, T> | MutableGraph<N, E, T>
|
|
2995
|
+
) => Option.Option<N>
|
|
729
2996
|
/**
|
|
730
2997
|
* Gets the data associated with a node index safely, if it exists.
|
|
731
2998
|
*
|
|
@@ -753,7 +3020,10 @@ export const getNode: {
|
|
|
753
3020
|
} = dual(2, <N, E, T extends Kind = "directed">(
|
|
754
3021
|
graph: Graph<N, E, T> | MutableGraph<N, E, T>,
|
|
755
3022
|
nodeIndex: NodeIndex
|
|
756
|
-
): Option.Option<N> =>
|
|
3023
|
+
): Option.Option<N> => {
|
|
3024
|
+
const impl = graphImpl(graph)
|
|
3025
|
+
return impl.nodes.has(nodeIndex) ? Option.some(impl.nodes.get(nodeIndex)!) : Option.none()
|
|
3026
|
+
})
|
|
757
3027
|
|
|
758
3028
|
/**
|
|
759
3029
|
* Checks whether a node with the given index exists in the graph.
|
|
@@ -833,7 +3103,7 @@ export const hasNode: {
|
|
|
833
3103
|
} = dual(2, <N, E, T extends Kind = "directed">(
|
|
834
3104
|
graph: Graph<N, E, T> | MutableGraph<N, E, T>,
|
|
835
3105
|
nodeIndex: NodeIndex
|
|
836
|
-
): boolean => graph.nodes.has(nodeIndex))
|
|
3106
|
+
): boolean => graphImpl(graph).nodes.has(nodeIndex))
|
|
837
3107
|
|
|
838
3108
|
/**
|
|
839
3109
|
* Returns the number of nodes in the graph.
|
|
@@ -860,7 +3130,7 @@ export const hasNode: {
|
|
|
860
3130
|
*/
|
|
861
3131
|
export const nodeCount = <N, E, T extends Kind = "directed">(
|
|
862
3132
|
graph: Graph<N, E, T> | MutableGraph<N, E, T>
|
|
863
|
-
): number => graph.nodes.size
|
|
3133
|
+
): number => graphImpl(graph).nodes.size
|
|
864
3134
|
|
|
865
3135
|
/**
|
|
866
3136
|
* Finds the first node that matches the given predicate.
|
|
@@ -944,7 +3214,8 @@ export const findNode: {
|
|
|
944
3214
|
graph: Graph<N, E, T> | MutableGraph<N, E, T>,
|
|
945
3215
|
predicate: (data: N) => boolean
|
|
946
3216
|
): Option.Option<NodeIndex> => {
|
|
947
|
-
|
|
3217
|
+
const impl = graphImpl(graph)
|
|
3218
|
+
for (const [index, data] of impl.nodes) {
|
|
948
3219
|
if (predicate(data)) {
|
|
949
3220
|
return Option.some(index)
|
|
950
3221
|
}
|
|
@@ -1034,8 +3305,9 @@ export const findNodes: {
|
|
|
1034
3305
|
graph: Graph<N, E, T> | MutableGraph<N, E, T>,
|
|
1035
3306
|
predicate: (data: N) => boolean
|
|
1036
3307
|
): Array<NodeIndex> => {
|
|
3308
|
+
const impl = graphImpl(graph)
|
|
1037
3309
|
const results: Array<NodeIndex> = []
|
|
1038
|
-
for (const [index, data] of
|
|
3310
|
+
for (const [index, data] of impl.nodes) {
|
|
1039
3311
|
if (predicate(data)) {
|
|
1040
3312
|
results.push(index)
|
|
1041
3313
|
}
|
|
@@ -1131,7 +3403,8 @@ export const findEdge: {
|
|
|
1131
3403
|
graph: Graph<N, E, T> | MutableGraph<N, E, T>,
|
|
1132
3404
|
predicate: (data: E, source: NodeIndex, target: NodeIndex) => boolean
|
|
1133
3405
|
): Option.Option<EdgeIndex> => {
|
|
1134
|
-
|
|
3406
|
+
const impl = graphImpl(graph)
|
|
3407
|
+
for (const [edgeIndex, edgeData] of impl.edges) {
|
|
1135
3408
|
if (predicate(edgeData.data, edgeData.source, edgeData.target)) {
|
|
1136
3409
|
return Option.some(edgeIndex)
|
|
1137
3410
|
}
|
|
@@ -1230,8 +3503,9 @@ export const findEdges: {
|
|
|
1230
3503
|
graph: Graph<N, E, T> | MutableGraph<N, E, T>,
|
|
1231
3504
|
predicate: (data: E, source: NodeIndex, target: NodeIndex) => boolean
|
|
1232
3505
|
): Array<EdgeIndex> => {
|
|
3506
|
+
const impl = graphImpl(graph)
|
|
1233
3507
|
const results: Array<EdgeIndex> = []
|
|
1234
|
-
for (const [edgeIndex, edgeData] of
|
|
3508
|
+
for (const [edgeIndex, edgeData] of impl.edges) {
|
|
1235
3509
|
if (predicate(edgeData.data, edgeData.source, edgeData.target)) {
|
|
1236
3510
|
results.push(edgeIndex)
|
|
1237
3511
|
}
|
|
@@ -1265,13 +3539,16 @@ export const updateNode = <N, E, T extends Kind = "directed">(
|
|
|
1265
3539
|
index: NodeIndex,
|
|
1266
3540
|
f: (data: N) => N
|
|
1267
3541
|
): void => {
|
|
1268
|
-
|
|
3542
|
+
assertMutable(mutable)
|
|
3543
|
+
const impl = graphImpl(mutable)
|
|
3544
|
+
|
|
3545
|
+
if (!impl.nodes.has(index)) {
|
|
1269
3546
|
return
|
|
1270
3547
|
}
|
|
1271
3548
|
|
|
1272
|
-
const currentData =
|
|
3549
|
+
const currentData = impl.nodes.get(index)!
|
|
1273
3550
|
const newData = f(currentData)
|
|
1274
|
-
|
|
3551
|
+
impl.nodes.set(index, newData)
|
|
1275
3552
|
}
|
|
1276
3553
|
|
|
1277
3554
|
/**
|
|
@@ -1301,13 +3578,16 @@ export const updateEdge = <N, E, T extends Kind = "directed">(
|
|
|
1301
3578
|
edgeIndex: EdgeIndex,
|
|
1302
3579
|
f: (data: E) => E
|
|
1303
3580
|
): void => {
|
|
1304
|
-
|
|
3581
|
+
assertMutable(mutable)
|
|
3582
|
+
const impl = graphImpl(mutable)
|
|
3583
|
+
|
|
3584
|
+
if (!impl.edges.has(edgeIndex)) {
|
|
1305
3585
|
return
|
|
1306
3586
|
}
|
|
1307
3587
|
|
|
1308
|
-
const currentEdge =
|
|
3588
|
+
const currentEdge = impl.edges.get(edgeIndex)!
|
|
1309
3589
|
const newData = f(currentEdge.data)
|
|
1310
|
-
|
|
3590
|
+
impl.edges.set(edgeIndex, new Edge({ ...currentEdge, data: newData }))
|
|
1311
3591
|
}
|
|
1312
3592
|
|
|
1313
3593
|
/**
|
|
@@ -1341,10 +3621,13 @@ export const mapNodes = <N, E, T extends Kind = "directed">(
|
|
|
1341
3621
|
mutable: MutableGraph<N, E, T>,
|
|
1342
3622
|
f: (data: N) => N
|
|
1343
3623
|
): void => {
|
|
3624
|
+
assertMutable(mutable)
|
|
3625
|
+
const impl = graphImpl(mutable)
|
|
3626
|
+
|
|
1344
3627
|
// Transform existing node data in place
|
|
1345
|
-
for (const [index, data] of
|
|
3628
|
+
for (const [index, data] of impl.nodes) {
|
|
1346
3629
|
const newData = f(data)
|
|
1347
|
-
|
|
3630
|
+
impl.nodes.set(index, newData)
|
|
1348
3631
|
}
|
|
1349
3632
|
}
|
|
1350
3633
|
|
|
@@ -1376,13 +3659,19 @@ export const mapEdges = <N, E, T extends Kind = "directed">(
|
|
|
1376
3659
|
mutable: MutableGraph<N, E, T>,
|
|
1377
3660
|
f: (data: E) => E
|
|
1378
3661
|
): void => {
|
|
3662
|
+
assertMutable(mutable)
|
|
3663
|
+
const impl = graphImpl(mutable)
|
|
3664
|
+
|
|
1379
3665
|
// Transform existing edge data in place
|
|
1380
|
-
for (const [index, edgeData] of
|
|
3666
|
+
for (const [index, edgeData] of impl.edges) {
|
|
1381
3667
|
const newData = f(edgeData.data)
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
3668
|
+
impl.edges.set(
|
|
3669
|
+
index,
|
|
3670
|
+
new Edge({
|
|
3671
|
+
...edgeData,
|
|
3672
|
+
data: newData
|
|
3673
|
+
})
|
|
3674
|
+
)
|
|
1386
3675
|
}
|
|
1387
3676
|
}
|
|
1388
3677
|
|
|
@@ -1390,7 +3679,7 @@ export const mapEdges = <N, E, T extends Kind = "directed">(
|
|
|
1390
3679
|
* @internal
|
|
1391
3680
|
*/
|
|
1392
3681
|
const rebuildAdjacency = <N, E, T extends Kind = "directed">(
|
|
1393
|
-
mutable:
|
|
3682
|
+
mutable: GraphImpl<N, E, T>
|
|
1394
3683
|
): void => {
|
|
1395
3684
|
mutable.adjacency.clear()
|
|
1396
3685
|
mutable.reverseAdjacency.clear()
|
|
@@ -1438,13 +3727,16 @@ const rebuildAdjacency = <N, E, T extends Kind = "directed">(
|
|
|
1438
3727
|
export const reverse = <N, E, T extends Kind = "directed">(
|
|
1439
3728
|
mutable: MutableGraph<N, E, T>
|
|
1440
3729
|
): void => {
|
|
1441
|
-
|
|
3730
|
+
assertMutable(mutable)
|
|
3731
|
+
const impl = graphImpl(mutable)
|
|
3732
|
+
|
|
3733
|
+
if (impl.type === "undirected") {
|
|
1442
3734
|
return
|
|
1443
3735
|
}
|
|
1444
3736
|
|
|
1445
3737
|
// Reverse all edges by swapping source and target
|
|
1446
|
-
for (const [index, edgeData] of
|
|
1447
|
-
|
|
3738
|
+
for (const [index, edgeData] of impl.edges) {
|
|
3739
|
+
impl.edges.set(
|
|
1448
3740
|
index,
|
|
1449
3741
|
new Edge({
|
|
1450
3742
|
source: edgeData.target,
|
|
@@ -1454,10 +3746,10 @@ export const reverse = <N, E, T extends Kind = "directed">(
|
|
|
1454
3746
|
)
|
|
1455
3747
|
}
|
|
1456
3748
|
|
|
1457
|
-
rebuildAdjacency(
|
|
3749
|
+
rebuildAdjacency(impl)
|
|
1458
3750
|
|
|
1459
3751
|
// Invalidate cycle flag since edge directions changed
|
|
1460
|
-
|
|
3752
|
+
impl.acyclic = Option.none()
|
|
1461
3753
|
}
|
|
1462
3754
|
|
|
1463
3755
|
/**
|
|
@@ -1494,14 +3786,17 @@ export const filterMapNodes = <N, E, T extends Kind = "directed">(
|
|
|
1494
3786
|
mutable: MutableGraph<N, E, T>,
|
|
1495
3787
|
f: (data: N) => Option.Option<N>
|
|
1496
3788
|
): void => {
|
|
3789
|
+
assertMutable(mutable)
|
|
3790
|
+
const impl = graphImpl(mutable)
|
|
3791
|
+
|
|
1497
3792
|
const nodesToRemove: Array<NodeIndex> = []
|
|
1498
3793
|
|
|
1499
3794
|
// First pass: identify nodes to remove and transform data for nodes to keep
|
|
1500
|
-
for (const [index, data] of
|
|
3795
|
+
for (const [index, data] of impl.nodes) {
|
|
1501
3796
|
const result = f(data)
|
|
1502
3797
|
if (Option.isSome(result)) {
|
|
1503
3798
|
// Transform node data
|
|
1504
|
-
|
|
3799
|
+
impl.nodes.set(index, result.value)
|
|
1505
3800
|
} else {
|
|
1506
3801
|
// Mark for removal
|
|
1507
3802
|
nodesToRemove.push(index)
|
|
@@ -1548,17 +3843,23 @@ export const filterMapEdges = <N, E, T extends Kind = "directed">(
|
|
|
1548
3843
|
mutable: MutableGraph<N, E, T>,
|
|
1549
3844
|
f: (data: E) => Option.Option<E>
|
|
1550
3845
|
): void => {
|
|
3846
|
+
assertMutable(mutable)
|
|
3847
|
+
const impl = graphImpl(mutable)
|
|
3848
|
+
|
|
1551
3849
|
const edgesToRemove: Array<EdgeIndex> = []
|
|
1552
3850
|
|
|
1553
3851
|
// First pass: identify edges to remove and transform data for edges to keep
|
|
1554
|
-
for (const [index, edgeData] of
|
|
3852
|
+
for (const [index, edgeData] of impl.edges) {
|
|
1555
3853
|
const result = f(edgeData.data)
|
|
1556
3854
|
if (Option.isSome(result)) {
|
|
1557
3855
|
// Transform edge data
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
3856
|
+
impl.edges.set(
|
|
3857
|
+
index,
|
|
3858
|
+
new Edge({
|
|
3859
|
+
...edgeData,
|
|
3860
|
+
data: result.value
|
|
3861
|
+
})
|
|
3862
|
+
)
|
|
1562
3863
|
} else {
|
|
1563
3864
|
// Mark for removal
|
|
1564
3865
|
edgesToRemove.push(index)
|
|
@@ -1600,10 +3901,13 @@ export const filterNodes = <N, E, T extends Kind = "directed">(
|
|
|
1600
3901
|
mutable: MutableGraph<N, E, T>,
|
|
1601
3902
|
predicate: (data: N) => boolean
|
|
1602
3903
|
): void => {
|
|
3904
|
+
assertMutable(mutable)
|
|
3905
|
+
const impl = graphImpl(mutable)
|
|
3906
|
+
|
|
1603
3907
|
const nodesToRemove: Array<NodeIndex> = []
|
|
1604
3908
|
|
|
1605
3909
|
// Identify nodes to remove
|
|
1606
|
-
for (const [index, data] of
|
|
3910
|
+
for (const [index, data] of impl.nodes) {
|
|
1607
3911
|
if (!predicate(data)) {
|
|
1608
3912
|
nodesToRemove.push(index)
|
|
1609
3913
|
}
|
|
@@ -1647,10 +3951,13 @@ export const filterEdges = <N, E, T extends Kind = "directed">(
|
|
|
1647
3951
|
mutable: MutableGraph<N, E, T>,
|
|
1648
3952
|
predicate: (data: E) => boolean
|
|
1649
3953
|
): void => {
|
|
3954
|
+
assertMutable(mutable)
|
|
3955
|
+
const impl = graphImpl(mutable)
|
|
3956
|
+
|
|
1650
3957
|
const edgesToRemove: Array<EdgeIndex> = []
|
|
1651
3958
|
|
|
1652
3959
|
// Identify edges to remove
|
|
1653
|
-
for (const [index, edgeData] of
|
|
3960
|
+
for (const [index, edgeData] of impl.edges) {
|
|
1654
3961
|
if (!predicate(edgeData.data)) {
|
|
1655
3962
|
edgesToRemove.push(index)
|
|
1656
3963
|
}
|
|
@@ -1668,7 +3975,7 @@ export const filterEdges = <N, E, T extends Kind = "directed">(
|
|
|
1668
3975
|
|
|
1669
3976
|
/** @internal */
|
|
1670
3977
|
const invalidateCycleFlagOnRemoval = <N, E, T extends Kind = "directed">(
|
|
1671
|
-
mutable:
|
|
3978
|
+
mutable: GraphImpl<N, E, T>
|
|
1672
3979
|
): void => {
|
|
1673
3980
|
// Only invalidate if the graph had cycles (removing edges/nodes cannot introduce cycles in acyclic graphs).
|
|
1674
3981
|
if (mutable.acyclic._tag === "Some" && mutable.acyclic.value === false) {
|
|
@@ -1678,7 +3985,7 @@ const invalidateCycleFlagOnRemoval = <N, E, T extends Kind = "directed">(
|
|
|
1678
3985
|
|
|
1679
3986
|
/** @internal */
|
|
1680
3987
|
const invalidateCycleFlagOnAddition = <N, E, T extends Kind = "directed">(
|
|
1681
|
-
mutable:
|
|
3988
|
+
mutable: GraphImpl<N, E, T>
|
|
1682
3989
|
): void => {
|
|
1683
3990
|
// Only invalidate if the graph was acyclic (adding edges cannot remove cycles from cyclic graphs).
|
|
1684
3991
|
if (mutable.acyclic._tag === "Some" && mutable.acyclic.value === true) {
|
|
@@ -1736,50 +4043,53 @@ export const addEdge = <N, E, T extends Kind = "directed">(
|
|
|
1736
4043
|
target: NodeIndex,
|
|
1737
4044
|
data: E
|
|
1738
4045
|
): EdgeIndex => {
|
|
4046
|
+
assertMutable(mutable)
|
|
4047
|
+
const impl = graphImpl(mutable)
|
|
4048
|
+
|
|
1739
4049
|
// Validate that both nodes exist
|
|
1740
|
-
if (!
|
|
4050
|
+
if (!impl.nodes.has(source)) {
|
|
1741
4051
|
throw missingNode(source)
|
|
1742
4052
|
}
|
|
1743
|
-
if (!
|
|
4053
|
+
if (!impl.nodes.has(target)) {
|
|
1744
4054
|
throw missingNode(target)
|
|
1745
4055
|
}
|
|
1746
4056
|
|
|
1747
|
-
const edgeIndex =
|
|
4057
|
+
const edgeIndex = impl.nextEdgeIndex
|
|
1748
4058
|
|
|
1749
4059
|
// Create edge data
|
|
1750
4060
|
const edgeData = new Edge({ source, target, data })
|
|
1751
|
-
|
|
4061
|
+
impl.edges.set(edgeIndex, edgeData)
|
|
1752
4062
|
|
|
1753
4063
|
// Update adjacency lists
|
|
1754
|
-
const sourceAdjacency =
|
|
4064
|
+
const sourceAdjacency = impl.adjacency.get(source)
|
|
1755
4065
|
if (sourceAdjacency !== undefined) {
|
|
1756
4066
|
sourceAdjacency.push(edgeIndex)
|
|
1757
4067
|
}
|
|
1758
4068
|
|
|
1759
|
-
const targetReverseAdjacency =
|
|
4069
|
+
const targetReverseAdjacency = impl.reverseAdjacency.get(target)
|
|
1760
4070
|
if (targetReverseAdjacency !== undefined) {
|
|
1761
4071
|
targetReverseAdjacency.push(edgeIndex)
|
|
1762
4072
|
}
|
|
1763
4073
|
|
|
1764
4074
|
// For undirected graphs, add reverse connections
|
|
1765
|
-
if (
|
|
1766
|
-
const targetAdjacency =
|
|
4075
|
+
if (impl.type === "undirected") {
|
|
4076
|
+
const targetAdjacency = impl.adjacency.get(target)
|
|
1767
4077
|
if (targetAdjacency !== undefined) {
|
|
1768
4078
|
targetAdjacency.push(edgeIndex)
|
|
1769
4079
|
}
|
|
1770
4080
|
|
|
1771
|
-
const sourceReverseAdjacency =
|
|
4081
|
+
const sourceReverseAdjacency = impl.reverseAdjacency.get(source)
|
|
1772
4082
|
if (sourceReverseAdjacency !== undefined) {
|
|
1773
4083
|
sourceReverseAdjacency.push(edgeIndex)
|
|
1774
4084
|
}
|
|
1775
4085
|
}
|
|
1776
4086
|
|
|
1777
4087
|
// Update allocators
|
|
1778
|
-
|
|
4088
|
+
impl.nextEdgeIndex = impl.nextEdgeIndex + 1
|
|
1779
4089
|
|
|
1780
4090
|
// Only invalidate cycle flag if the graph was acyclic
|
|
1781
4091
|
// Adding edges cannot remove cycles from cyclic graphs
|
|
1782
|
-
invalidateCycleFlagOnAddition(
|
|
4092
|
+
invalidateCycleFlagOnAddition(impl)
|
|
1783
4093
|
|
|
1784
4094
|
return edgeIndex
|
|
1785
4095
|
}
|
|
@@ -1809,8 +4119,11 @@ export const removeNode = <N, E, T extends Kind = "directed">(
|
|
|
1809
4119
|
mutable: MutableGraph<N, E, T>,
|
|
1810
4120
|
nodeIndex: NodeIndex
|
|
1811
4121
|
): void => {
|
|
4122
|
+
assertMutable(mutable)
|
|
4123
|
+
const impl = graphImpl(mutable)
|
|
4124
|
+
|
|
1812
4125
|
// Check if node exists
|
|
1813
|
-
if (!
|
|
4126
|
+
if (!impl.nodes.has(nodeIndex)) {
|
|
1814
4127
|
return // Node doesn't exist, nothing to remove
|
|
1815
4128
|
}
|
|
1816
4129
|
|
|
@@ -1818,7 +4131,7 @@ export const removeNode = <N, E, T extends Kind = "directed">(
|
|
|
1818
4131
|
const edgesToRemove: Array<EdgeIndex> = []
|
|
1819
4132
|
|
|
1820
4133
|
// Get outgoing edges
|
|
1821
|
-
const outgoingEdges =
|
|
4134
|
+
const outgoingEdges = impl.adjacency.get(nodeIndex)
|
|
1822
4135
|
if (outgoingEdges !== undefined) {
|
|
1823
4136
|
for (const edge of outgoingEdges) {
|
|
1824
4137
|
edgesToRemove.push(edge)
|
|
@@ -1826,7 +4139,7 @@ export const removeNode = <N, E, T extends Kind = "directed">(
|
|
|
1826
4139
|
}
|
|
1827
4140
|
|
|
1828
4141
|
// Get incoming edges
|
|
1829
|
-
const incomingEdges =
|
|
4142
|
+
const incomingEdges = impl.reverseAdjacency.get(nodeIndex)
|
|
1830
4143
|
if (incomingEdges !== undefined) {
|
|
1831
4144
|
for (const edge of incomingEdges) {
|
|
1832
4145
|
edgesToRemove.push(edge)
|
|
@@ -1835,17 +4148,17 @@ export const removeNode = <N, E, T extends Kind = "directed">(
|
|
|
1835
4148
|
|
|
1836
4149
|
// Remove all incident edges
|
|
1837
4150
|
for (const edgeIndex of edgesToRemove) {
|
|
1838
|
-
removeEdgeInternal(
|
|
4151
|
+
removeEdgeInternal(impl, edgeIndex)
|
|
1839
4152
|
}
|
|
1840
4153
|
|
|
1841
4154
|
// Remove the node itself
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
4155
|
+
impl.nodes.delete(nodeIndex)
|
|
4156
|
+
impl.adjacency.delete(nodeIndex)
|
|
4157
|
+
impl.reverseAdjacency.delete(nodeIndex)
|
|
1845
4158
|
|
|
1846
4159
|
// Only invalidate cycle flag if the graph wasn't already known to be acyclic
|
|
1847
4160
|
// Removing nodes cannot introduce cycles in an acyclic graph
|
|
1848
|
-
invalidateCycleFlagOnRemoval(
|
|
4161
|
+
invalidateCycleFlagOnRemoval(impl)
|
|
1849
4162
|
}
|
|
1850
4163
|
|
|
1851
4164
|
/**
|
|
@@ -1873,18 +4186,21 @@ export const removeEdge = <N, E, T extends Kind = "directed">(
|
|
|
1873
4186
|
mutable: MutableGraph<N, E, T>,
|
|
1874
4187
|
edgeIndex: EdgeIndex
|
|
1875
4188
|
): void => {
|
|
1876
|
-
|
|
4189
|
+
assertMutable(mutable)
|
|
4190
|
+
const impl = graphImpl(mutable)
|
|
4191
|
+
|
|
4192
|
+
const wasRemoved = removeEdgeInternal(impl, edgeIndex)
|
|
1877
4193
|
|
|
1878
4194
|
// Only invalidate cycle flag if an edge was actually removed
|
|
1879
4195
|
// and only if the graph wasn't already known to be acyclic
|
|
1880
4196
|
if (wasRemoved) {
|
|
1881
|
-
invalidateCycleFlagOnRemoval(
|
|
4197
|
+
invalidateCycleFlagOnRemoval(impl)
|
|
1882
4198
|
}
|
|
1883
4199
|
}
|
|
1884
4200
|
|
|
1885
4201
|
/** @internal */
|
|
1886
4202
|
const removeEdgeInternal = <N, E, T extends Kind = "directed">(
|
|
1887
|
-
mutable:
|
|
4203
|
+
mutable: GraphImpl<N, E, T>,
|
|
1888
4204
|
edgeIndex: EdgeIndex
|
|
1889
4205
|
): boolean => {
|
|
1890
4206
|
// Get edge data
|
|
@@ -2000,7 +4316,9 @@ export const getEdge: {
|
|
|
2000
4316
|
* @category getters
|
|
2001
4317
|
* @since 3.18.0
|
|
2002
4318
|
*/
|
|
2003
|
-
|
|
4319
|
+
(edgeIndex: EdgeIndex): <N, E, T extends Kind = "directed">(
|
|
4320
|
+
graph: Graph<N, E, T> | MutableGraph<N, E, T>
|
|
4321
|
+
) => Option.Option<Edge<E>>
|
|
2004
4322
|
// =============================================================================
|
|
2005
4323
|
// Edge Query Operations
|
|
2006
4324
|
// =============================================================================
|
|
@@ -2036,7 +4354,7 @@ export const getEdge: {
|
|
|
2036
4354
|
} = dual(2, <N, E, T extends Kind = "directed">(
|
|
2037
4355
|
graph: Graph<N, E, T> | MutableGraph<N, E, T>,
|
|
2038
4356
|
edgeIndex: EdgeIndex
|
|
2039
|
-
): Option.Option<Edge<E>> => Option.fromUndefinedOr(graph.edges.get(edgeIndex)))
|
|
4357
|
+
): Option.Option<Edge<E>> => Option.fromUndefinedOr(graphImpl(graph).edges.get(edgeIndex)))
|
|
2040
4358
|
|
|
2041
4359
|
/**
|
|
2042
4360
|
* Checks whether an edge exists between two nodes in the graph.
|
|
@@ -2137,14 +4455,15 @@ export const hasEdge: {
|
|
|
2137
4455
|
source: NodeIndex,
|
|
2138
4456
|
target: NodeIndex
|
|
2139
4457
|
): boolean => {
|
|
2140
|
-
const
|
|
4458
|
+
const impl = graphImpl(graph)
|
|
4459
|
+
const adjacencyList = impl.adjacency.get(source)
|
|
2141
4460
|
if (adjacencyList === undefined) {
|
|
2142
4461
|
return false
|
|
2143
4462
|
}
|
|
2144
4463
|
|
|
2145
4464
|
// Check if any edge in the adjacency list connects to the target
|
|
2146
4465
|
for (const edgeIndex of adjacencyList) {
|
|
2147
|
-
const edge =
|
|
4466
|
+
const edge = impl.edges.get(edgeIndex)
|
|
2148
4467
|
if (edge !== undefined) {
|
|
2149
4468
|
const neighbor = graph.type === "undirected" && edge.target === source ? edge.source : edge.target
|
|
2150
4469
|
if (neighbor === target) {
|
|
@@ -2184,16 +4503,17 @@ export const hasEdge: {
|
|
|
2184
4503
|
*/
|
|
2185
4504
|
export const edgeCount = <N, E, T extends Kind = "directed">(
|
|
2186
4505
|
graph: Graph<N, E, T> | MutableGraph<N, E, T>
|
|
2187
|
-
): number => graph.edges.size
|
|
4506
|
+
): number => graphImpl(graph).edges.size
|
|
2188
4507
|
|
|
2189
4508
|
const getDirectedNeighbors = <N, E>(
|
|
2190
4509
|
graph: Graph<N, E, "directed"> | MutableGraph<N, E, "directed">,
|
|
2191
4510
|
nodeIndex: NodeIndex,
|
|
2192
4511
|
direction: Direction
|
|
2193
4512
|
): Array<NodeIndex> => {
|
|
4513
|
+
const impl = graphImpl(graph)
|
|
2194
4514
|
const adjacencyMap = direction === "incoming"
|
|
2195
|
-
?
|
|
2196
|
-
:
|
|
4515
|
+
? impl.reverseAdjacency
|
|
4516
|
+
: impl.adjacency
|
|
2197
4517
|
|
|
2198
4518
|
const adjacencyList = adjacencyMap.get(nodeIndex)
|
|
2199
4519
|
if (adjacencyList === undefined) {
|
|
@@ -2202,7 +4522,7 @@ const getDirectedNeighbors = <N, E>(
|
|
|
2202
4522
|
|
|
2203
4523
|
const result: Array<NodeIndex> = []
|
|
2204
4524
|
for (const edgeIndex of adjacencyList) {
|
|
2205
|
-
const edge =
|
|
4525
|
+
const edge = impl.edges.get(edgeIndex)
|
|
2206
4526
|
if (edge !== undefined) {
|
|
2207
4527
|
result.push(direction === "incoming" ? edge.source : edge.target)
|
|
2208
4528
|
}
|
|
@@ -2660,6 +4980,9 @@ export interface GraphVizOptions<N, E> {
|
|
|
2660
4980
|
readonly graphName?: string
|
|
2661
4981
|
}
|
|
2662
4982
|
|
|
4983
|
+
const escapeGraphVizString = (value: string): string =>
|
|
4984
|
+
value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\r\n|\r|\n/g, "\\n")
|
|
4985
|
+
|
|
2663
4986
|
/**
|
|
2664
4987
|
* Exports a graph to GraphViz DOT format for visualization.
|
|
2665
4988
|
*
|
|
@@ -2679,7 +5002,7 @@ export interface GraphVizOptions<N, E> {
|
|
|
2679
5002
|
*
|
|
2680
5003
|
* const dot = Graph.toGraphViz(graph)
|
|
2681
5004
|
* console.log(dot)
|
|
2682
|
-
* // digraph G {
|
|
5005
|
+
* // digraph "G" {
|
|
2683
5006
|
* // "0" [label="Node A"];
|
|
2684
5007
|
* // "1" [label="Node B"];
|
|
2685
5008
|
* // "2" [label="Node C"];
|
|
@@ -2712,7 +5035,7 @@ export const toGraphViz: {
|
|
|
2712
5035
|
*
|
|
2713
5036
|
* const dot = Graph.toGraphViz(graph)
|
|
2714
5037
|
* console.log(dot)
|
|
2715
|
-
* // digraph G {
|
|
5038
|
+
* // digraph "G" {
|
|
2716
5039
|
* // "0" [label="Node A"];
|
|
2717
5040
|
* // "1" [label="Node B"];
|
|
2718
5041
|
* // "2" [label="Node C"];
|
|
@@ -2745,7 +5068,7 @@ export const toGraphViz: {
|
|
|
2745
5068
|
*
|
|
2746
5069
|
* const dot = Graph.toGraphViz(graph)
|
|
2747
5070
|
* console.log(dot)
|
|
2748
|
-
* // digraph G {
|
|
5071
|
+
* // digraph "G" {
|
|
2749
5072
|
* // "0" [label="Node A"];
|
|
2750
5073
|
* // "1" [label="Node B"];
|
|
2751
5074
|
* // "2" [label="Node C"];
|
|
@@ -2766,6 +5089,7 @@ export const toGraphViz: {
|
|
|
2766
5089
|
graph: Graph<N, E, T> | MutableGraph<N, E, T>,
|
|
2767
5090
|
options?: GraphVizOptions<N, E>
|
|
2768
5091
|
): string => {
|
|
5092
|
+
const impl = graphImpl(graph)
|
|
2769
5093
|
const {
|
|
2770
5094
|
edgeLabel = (data: E) => String(data),
|
|
2771
5095
|
graphName = "G",
|
|
@@ -2775,19 +5099,20 @@ export const toGraphViz: {
|
|
|
2775
5099
|
const isDirected = graph.type === "directed"
|
|
2776
5100
|
const graphType = isDirected ? "digraph" : "graph"
|
|
2777
5101
|
const edgeOperator = isDirected ? "->" : "--"
|
|
5102
|
+
const graphId = `"${escapeGraphVizString(graphName)}"`
|
|
2778
5103
|
|
|
2779
5104
|
const lines: Array<string> = []
|
|
2780
|
-
lines.push(`${graphType} ${
|
|
5105
|
+
lines.push(`${graphType} ${graphId} {`)
|
|
2781
5106
|
|
|
2782
5107
|
// Add nodes
|
|
2783
|
-
for (const [nodeIndex, nodeData] of
|
|
2784
|
-
const label = nodeLabel(nodeData)
|
|
5108
|
+
for (const [nodeIndex, nodeData] of impl.nodes) {
|
|
5109
|
+
const label = escapeGraphVizString(nodeLabel(nodeData))
|
|
2785
5110
|
lines.push(` "${nodeIndex}" [label="${label}"];`)
|
|
2786
5111
|
}
|
|
2787
5112
|
|
|
2788
5113
|
// Add edges
|
|
2789
|
-
for (const [, edgeData] of
|
|
2790
|
-
const label = edgeLabel(edgeData.data)
|
|
5114
|
+
for (const [, edgeData] of impl.edges) {
|
|
5115
|
+
const label = escapeGraphVizString(edgeLabel(edgeData.data))
|
|
2791
5116
|
lines.push(` "${edgeData.source}" ${edgeOperator} "${edgeData.target}" [label="${label}"];`)
|
|
2792
5117
|
}
|
|
2793
5118
|
|
|
@@ -3181,22 +5506,22 @@ const formatMermaidNode = (
|
|
|
3181
5506
|
* name: "MyApp",
|
|
3182
5507
|
* version: "1.0.0",
|
|
3183
5508
|
* type: "library"
|
|
3184
|
-
* })
|
|
5509
|
+
* } satisfies Dependency)
|
|
3185
5510
|
* const react = Graph.addNode(mutable, {
|
|
3186
5511
|
* name: "React",
|
|
3187
5512
|
* version: "18.0.0",
|
|
3188
5513
|
* type: "framework"
|
|
3189
|
-
* })
|
|
5514
|
+
* } satisfies Dependency)
|
|
3190
5515
|
* const lodash = Graph.addNode(mutable, {
|
|
3191
5516
|
* name: "Lodash",
|
|
3192
5517
|
* version: "4.17.0",
|
|
3193
5518
|
* type: "library"
|
|
3194
|
-
* })
|
|
5519
|
+
* } satisfies Dependency)
|
|
3195
5520
|
* const webpack = Graph.addNode(mutable, {
|
|
3196
5521
|
* name: "Webpack",
|
|
3197
5522
|
* version: "5.0.0",
|
|
3198
5523
|
* type: "tool"
|
|
3199
|
-
* })
|
|
5524
|
+
* } satisfies Dependency)
|
|
3200
5525
|
*
|
|
3201
5526
|
* Graph.addEdge(mutable, app, react, "depends on")
|
|
3202
5527
|
* Graph.addEdge(mutable, app, lodash, "depends on")
|
|
@@ -3361,22 +5686,22 @@ export const toMermaid: {
|
|
|
3361
5686
|
* name: "MyApp",
|
|
3362
5687
|
* version: "1.0.0",
|
|
3363
5688
|
* type: "library"
|
|
3364
|
-
* })
|
|
5689
|
+
* } satisfies Dependency)
|
|
3365
5690
|
* const react = Graph.addNode(mutable, {
|
|
3366
5691
|
* name: "React",
|
|
3367
5692
|
* version: "18.0.0",
|
|
3368
5693
|
* type: "framework"
|
|
3369
|
-
* })
|
|
5694
|
+
* } satisfies Dependency)
|
|
3370
5695
|
* const lodash = Graph.addNode(mutable, {
|
|
3371
5696
|
* name: "Lodash",
|
|
3372
5697
|
* version: "4.17.0",
|
|
3373
5698
|
* type: "library"
|
|
3374
|
-
* })
|
|
5699
|
+
* } satisfies Dependency)
|
|
3375
5700
|
* const webpack = Graph.addNode(mutable, {
|
|
3376
5701
|
* name: "Webpack",
|
|
3377
5702
|
* version: "5.0.0",
|
|
3378
5703
|
* type: "tool"
|
|
3379
|
-
* })
|
|
5704
|
+
* } satisfies Dependency)
|
|
3380
5705
|
*
|
|
3381
5706
|
* Graph.addEdge(mutable, app, react, "depends on")
|
|
3382
5707
|
* Graph.addEdge(mutable, app, lodash, "depends on")
|
|
@@ -3541,22 +5866,22 @@ export const toMermaid: {
|
|
|
3541
5866
|
* name: "MyApp",
|
|
3542
5867
|
* version: "1.0.0",
|
|
3543
5868
|
* type: "library"
|
|
3544
|
-
* })
|
|
5869
|
+
* } satisfies Dependency)
|
|
3545
5870
|
* const react = Graph.addNode(mutable, {
|
|
3546
5871
|
* name: "React",
|
|
3547
5872
|
* version: "18.0.0",
|
|
3548
5873
|
* type: "framework"
|
|
3549
|
-
* })
|
|
5874
|
+
* } satisfies Dependency)
|
|
3550
5875
|
* const lodash = Graph.addNode(mutable, {
|
|
3551
5876
|
* name: "Lodash",
|
|
3552
5877
|
* version: "4.17.0",
|
|
3553
5878
|
* type: "library"
|
|
3554
|
-
* })
|
|
5879
|
+
* } satisfies Dependency)
|
|
3555
5880
|
* const webpack = Graph.addNode(mutable, {
|
|
3556
5881
|
* name: "Webpack",
|
|
3557
5882
|
* version: "5.0.0",
|
|
3558
5883
|
* type: "tool"
|
|
3559
|
-
* })
|
|
5884
|
+
* } satisfies Dependency)
|
|
3560
5885
|
*
|
|
3561
5886
|
* Graph.addEdge(mutable, app, react, "depends on")
|
|
3562
5887
|
* Graph.addEdge(mutable, app, lodash, "depends on")
|
|
@@ -3597,6 +5922,7 @@ export const toMermaid: {
|
|
|
3597
5922
|
graph: Graph<N, E, T> | MutableGraph<N, E, T>,
|
|
3598
5923
|
options?: MermaidOptions<N, E>
|
|
3599
5924
|
): string => {
|
|
5925
|
+
const impl = graphImpl(graph)
|
|
3600
5926
|
// Extract and validate options with defaults
|
|
3601
5927
|
const {
|
|
3602
5928
|
diagramType,
|
|
@@ -3615,7 +5941,7 @@ export const toMermaid: {
|
|
|
3615
5941
|
lines.push(`${finalDiagramType} ${direction}`)
|
|
3616
5942
|
|
|
3617
5943
|
// Add nodes
|
|
3618
|
-
for (const [nodeIndex, nodeData] of
|
|
5944
|
+
for (const [nodeIndex, nodeData] of impl.nodes) {
|
|
3619
5945
|
const nodeId = String(nodeIndex)
|
|
3620
5946
|
const label = escapeMermaidLabel(nodeLabel(nodeData))
|
|
3621
5947
|
const shape = nodeShape(nodeData)
|
|
@@ -3625,7 +5951,7 @@ export const toMermaid: {
|
|
|
3625
5951
|
|
|
3626
5952
|
// Add edges
|
|
3627
5953
|
const edgeOperator = finalDiagramType === "flowchart" ? "-->" : "---"
|
|
3628
|
-
for (const [, edgeData] of
|
|
5954
|
+
for (const [, edgeData] of impl.edges) {
|
|
3629
5955
|
const sourceId = String(edgeData.source)
|
|
3630
5956
|
const targetId = String(edgeData.target)
|
|
3631
5957
|
const label = escapeMermaidLabel(edgeLabel(edgeData.data))
|
|
@@ -3641,11 +5967,30 @@ export const toMermaid: {
|
|
|
3641
5967
|
})
|
|
3642
5968
|
|
|
3643
5969
|
// =============================================================================
|
|
3644
|
-
// Direction Types
|
|
5970
|
+
// Edge Direction Types
|
|
3645
5971
|
// =============================================================================
|
|
3646
5972
|
|
|
3647
5973
|
/**
|
|
3648
|
-
* Direction
|
|
5974
|
+
* Direction of directed edges relative to a node.
|
|
5975
|
+
*
|
|
5976
|
+
* **Details**
|
|
5977
|
+
*
|
|
5978
|
+
* `"outgoing"` selects edges whose source is the node, while `"incoming"`
|
|
5979
|
+
* selects edges whose target is the node.
|
|
5980
|
+
*
|
|
5981
|
+
* @category models
|
|
5982
|
+
* @since 3.18.0
|
|
5983
|
+
*/
|
|
5984
|
+
export type Direction = "outgoing" | "incoming"
|
|
5985
|
+
|
|
5986
|
+
/**
|
|
5987
|
+
* Controls how traversal follows directed edges.
|
|
5988
|
+
*
|
|
5989
|
+
* **Details**
|
|
5990
|
+
*
|
|
5991
|
+
* `"outgoing"` follows edges from source to target, `"incoming"` follows them
|
|
5992
|
+
* from target to source, and `"undirected"` allows traversal in either
|
|
5993
|
+
* direction.
|
|
3649
5994
|
*
|
|
3650
5995
|
* **Example** (Traversing by direction)
|
|
3651
5996
|
*
|
|
@@ -3655,24 +6000,28 @@ export const toMermaid: {
|
|
|
3655
6000
|
* const graph = Graph.directed<string, string>((mutable) => {
|
|
3656
6001
|
* const a = Graph.addNode(mutable, "A")
|
|
3657
6002
|
* const b = Graph.addNode(mutable, "B")
|
|
3658
|
-
* Graph.
|
|
6003
|
+
* const c = Graph.addNode(mutable, "C")
|
|
6004
|
+
* Graph.addEdge(mutable, a, b, "A-B")
|
|
6005
|
+
* Graph.addEdge(mutable, a, c, "A-C")
|
|
3659
6006
|
* })
|
|
3660
6007
|
*
|
|
3661
|
-
*
|
|
3662
|
-
*
|
|
3663
|
-
*
|
|
3664
|
-
* )
|
|
6008
|
+
* const outgoing = Array.from(
|
|
6009
|
+
* Graph.indices(Graph.bfs(graph, { start: [0], direction: "outgoing" }))
|
|
6010
|
+
* ) // [0, 1, 2]
|
|
3665
6011
|
*
|
|
3666
|
-
*
|
|
3667
|
-
*
|
|
3668
|
-
*
|
|
3669
|
-
*
|
|
6012
|
+
* const incoming = Array.from(
|
|
6013
|
+
* Graph.indices(Graph.bfs(graph, { start: [1], direction: "incoming" }))
|
|
6014
|
+
* ) // [1, 0]
|
|
6015
|
+
*
|
|
6016
|
+
* const undirected = Array.from(
|
|
6017
|
+
* Graph.indices(Graph.bfs(graph, { start: [1], direction: "undirected" }))
|
|
6018
|
+
* ) // [1, 0, 2]
|
|
3670
6019
|
* ```
|
|
3671
6020
|
*
|
|
3672
6021
|
* @category models
|
|
3673
|
-
* @since
|
|
6022
|
+
* @since 4.0.0
|
|
3674
6023
|
*/
|
|
3675
|
-
export type
|
|
6024
|
+
export type TraversalDirection = Direction | "undirected"
|
|
3676
6025
|
|
|
3677
6026
|
// =============================================================================
|
|
3678
6027
|
// Graph Structure Analysis Algorithms
|
|
@@ -3685,7 +6034,8 @@ export type Direction = "outgoing" | "incoming"
|
|
|
3685
6034
|
*
|
|
3686
6035
|
* Uses depth-first search to detect back edges, which indicate cycles.
|
|
3687
6036
|
* For directed graphs, any back edge creates a cycle. For undirected graphs,
|
|
3688
|
-
* a back edge that doesn't
|
|
6037
|
+
* a back edge that doesn't use the same edge used to enter the current node
|
|
6038
|
+
* creates a cycle.
|
|
3689
6039
|
*
|
|
3690
6040
|
* **Example** (Checking cycles)
|
|
3691
6041
|
*
|
|
@@ -3718,39 +6068,51 @@ export type Direction = "outgoing" | "incoming"
|
|
|
3718
6068
|
export const isAcyclic = <N, E, T extends Kind = "directed">(
|
|
3719
6069
|
graph: Graph<N, E, T> | MutableGraph<N, E, T>
|
|
3720
6070
|
): boolean => {
|
|
6071
|
+
const impl = graphImpl(graph)
|
|
3721
6072
|
// Use existing cycle flag if available
|
|
3722
|
-
if (Option.isSome(
|
|
3723
|
-
return
|
|
6073
|
+
if (Option.isSome(impl.acyclic)) {
|
|
6074
|
+
return impl.acyclic.value
|
|
3724
6075
|
}
|
|
3725
6076
|
|
|
3726
6077
|
if (graph.type === "undirected") {
|
|
3727
6078
|
const visited = new Set<NodeIndex>()
|
|
3728
6079
|
|
|
3729
|
-
for (const startNode of
|
|
6080
|
+
for (const startNode of impl.nodes.keys()) {
|
|
3730
6081
|
if (visited.has(startNode)) {
|
|
3731
6082
|
continue
|
|
3732
6083
|
}
|
|
3733
6084
|
|
|
3734
6085
|
visited.add(startNode)
|
|
3735
|
-
const stack: Array<{ node: NodeIndex;
|
|
6086
|
+
const stack: Array<{ node: NodeIndex; incoming: EdgeIndex | null }> = [{ node: startNode, incoming: null }]
|
|
3736
6087
|
|
|
3737
6088
|
while (stack.length > 0) {
|
|
3738
|
-
const { node,
|
|
3739
|
-
const
|
|
6089
|
+
const { node, incoming } = stack.pop()!
|
|
6090
|
+
const adjacencyList = impl.adjacency.get(node)
|
|
6091
|
+
if (adjacencyList === undefined) {
|
|
6092
|
+
continue
|
|
6093
|
+
}
|
|
3740
6094
|
|
|
3741
|
-
for (const
|
|
6095
|
+
for (const edgeIndex of adjacencyList) {
|
|
6096
|
+
if (edgeIndex === incoming) {
|
|
6097
|
+
continue
|
|
6098
|
+
}
|
|
6099
|
+
const edge = impl.edges.get(edgeIndex)
|
|
6100
|
+
if (edge === undefined) {
|
|
6101
|
+
continue
|
|
6102
|
+
}
|
|
6103
|
+
const neighbor = getTraversableNeighbor(graph, node, edge)
|
|
3742
6104
|
if (!visited.has(neighbor)) {
|
|
3743
6105
|
visited.add(neighbor)
|
|
3744
|
-
stack.push({ node: neighbor,
|
|
3745
|
-
} else
|
|
3746
|
-
|
|
6106
|
+
stack.push({ node: neighbor, incoming: edgeIndex })
|
|
6107
|
+
} else {
|
|
6108
|
+
impl.acyclic = Option.some(false)
|
|
3747
6109
|
return false
|
|
3748
6110
|
}
|
|
3749
6111
|
}
|
|
3750
6112
|
}
|
|
3751
6113
|
}
|
|
3752
6114
|
|
|
3753
|
-
|
|
6115
|
+
impl.acyclic = Option.some(true)
|
|
3754
6116
|
return true
|
|
3755
6117
|
}
|
|
3756
6118
|
|
|
@@ -3762,7 +6124,7 @@ export const isAcyclic = <N, E, T extends Kind = "directed">(
|
|
|
3762
6124
|
type DfsStackEntry = [NodeIndex, Array<NodeIndex>, number, boolean]
|
|
3763
6125
|
|
|
3764
6126
|
// Get all nodes to handle disconnected components
|
|
3765
|
-
for (const startNode of
|
|
6127
|
+
for (const startNode of impl.nodes.keys()) {
|
|
3766
6128
|
if (visited.has(startNode)) {
|
|
3767
6129
|
continue // Already processed this component
|
|
3768
6130
|
}
|
|
@@ -3777,7 +6139,7 @@ export const isAcyclic = <N, E, T extends Kind = "directed">(
|
|
|
3777
6139
|
if (isFirstVisit) {
|
|
3778
6140
|
if (recursionStack.has(node)) {
|
|
3779
6141
|
// Back edge found - cycle detected
|
|
3780
|
-
|
|
6142
|
+
impl.acyclic = Option.some(false)
|
|
3781
6143
|
return false
|
|
3782
6144
|
}
|
|
3783
6145
|
|
|
@@ -3806,7 +6168,7 @@ export const isAcyclic = <N, E, T extends Kind = "directed">(
|
|
|
3806
6168
|
|
|
3807
6169
|
if (recursionStack.has(neighbor)) {
|
|
3808
6170
|
// Back edge found - cycle detected
|
|
3809
|
-
|
|
6171
|
+
impl.acyclic = Option.some(false)
|
|
3810
6172
|
return false
|
|
3811
6173
|
}
|
|
3812
6174
|
|
|
@@ -3822,7 +6184,7 @@ export const isAcyclic = <N, E, T extends Kind = "directed">(
|
|
|
3822
6184
|
}
|
|
3823
6185
|
|
|
3824
6186
|
// Cache the result
|
|
3825
|
-
|
|
6187
|
+
impl.acyclic = Option.some(true)
|
|
3826
6188
|
return true
|
|
3827
6189
|
}
|
|
3828
6190
|
|
|
@@ -3870,12 +6232,13 @@ export const isAcyclic = <N, E, T extends Kind = "directed">(
|
|
|
3870
6232
|
export const isBipartite = <N, E>(
|
|
3871
6233
|
graph: Graph<N, E, "undirected"> | MutableGraph<N, E, "undirected">
|
|
3872
6234
|
): boolean => {
|
|
6235
|
+
const impl = graphImpl(graph)
|
|
3873
6236
|
const coloring = new Map<NodeIndex, 0 | 1>()
|
|
3874
6237
|
const discovered = new Set<NodeIndex>()
|
|
3875
6238
|
let isBipartiteGraph = true
|
|
3876
6239
|
|
|
3877
6240
|
// Get all nodes to handle disconnected components
|
|
3878
|
-
for (const startNode of
|
|
6241
|
+
for (const startNode of impl.nodes.keys()) {
|
|
3879
6242
|
if (!discovered.has(startNode)) {
|
|
3880
6243
|
// Start BFS coloring from this component
|
|
3881
6244
|
const queue: Array<NodeIndex> = [startNode]
|
|
@@ -3923,13 +6286,14 @@ const getUndirectedNeighbors = <N, E>(
|
|
|
3923
6286
|
graph: Graph<N, E, "undirected"> | MutableGraph<N, E, "undirected">,
|
|
3924
6287
|
nodeIndex: NodeIndex
|
|
3925
6288
|
): Array<NodeIndex> => {
|
|
6289
|
+
const impl = graphImpl(graph)
|
|
3926
6290
|
const neighbors = new Set<NodeIndex>()
|
|
3927
6291
|
|
|
3928
6292
|
// Check edges where this node is the source
|
|
3929
|
-
const adjacencyList =
|
|
6293
|
+
const adjacencyList = impl.adjacency.get(nodeIndex)
|
|
3930
6294
|
if (adjacencyList !== undefined) {
|
|
3931
6295
|
for (const edgeIndex of adjacencyList) {
|
|
3932
|
-
const edge =
|
|
6296
|
+
const edge = impl.edges.get(edgeIndex)
|
|
3933
6297
|
if (edge !== undefined) {
|
|
3934
6298
|
// For undirected graphs, the neighbor is the other endpoint
|
|
3935
6299
|
const otherNode = edge.source === nodeIndex ? edge.target : edge.source
|
|
@@ -3944,14 +6308,24 @@ const getUndirectedNeighbors = <N, E>(
|
|
|
3944
6308
|
const getTraversalNeighbors = <N, E, T extends Kind>(
|
|
3945
6309
|
graph: Graph<N, E, T> | MutableGraph<N, E, T>,
|
|
3946
6310
|
nodeIndex: NodeIndex,
|
|
3947
|
-
direction:
|
|
3948
|
-
): Array<NodeIndex> =>
|
|
3949
|
-
graph.type === "undirected"
|
|
3950
|
-
|
|
3951
|
-
|
|
6311
|
+
direction: TraversalDirection
|
|
6312
|
+
): Array<NodeIndex> => {
|
|
6313
|
+
if (graph.type === "undirected") {
|
|
6314
|
+
return getUndirectedNeighbors(graph as any, nodeIndex)
|
|
6315
|
+
}
|
|
6316
|
+
const directed = graph as Graph<N, E, "directed"> | MutableGraph<N, E, "directed">
|
|
6317
|
+
if (direction !== "undirected") {
|
|
6318
|
+
return getDirectedNeighbors(directed, nodeIndex, direction)
|
|
6319
|
+
}
|
|
6320
|
+
const neighbors = new Set(getDirectedNeighbors(directed, nodeIndex, "outgoing"))
|
|
6321
|
+
for (const neighbor of getDirectedNeighbors(directed, nodeIndex, "incoming")) {
|
|
6322
|
+
neighbors.add(neighbor)
|
|
6323
|
+
}
|
|
6324
|
+
return Array.from(neighbors)
|
|
6325
|
+
}
|
|
3952
6326
|
|
|
3953
|
-
const getTraversableNeighbor = <E, T extends Kind>(
|
|
3954
|
-
graph: Graph<
|
|
6327
|
+
const getTraversableNeighbor = <N, E, T extends Kind>(
|
|
6328
|
+
graph: Graph<N, E, T> | MutableGraph<N, E, T>,
|
|
3955
6329
|
current: NodeIndex,
|
|
3956
6330
|
edge: Edge<E>
|
|
3957
6331
|
): NodeIndex => graph.type === "undirected" && edge.target === current ? edge.source : edge.target
|
|
@@ -3984,9 +6358,10 @@ const getTraversableNeighbor = <E, T extends Kind>(
|
|
|
3984
6358
|
export const connectedComponents = <N, E>(
|
|
3985
6359
|
graph: Graph<N, E, "undirected"> | MutableGraph<N, E, "undirected">
|
|
3986
6360
|
): Array<Array<NodeIndex>> => {
|
|
6361
|
+
const impl = graphImpl(graph)
|
|
3987
6362
|
const visited = new Set<NodeIndex>()
|
|
3988
6363
|
const components: Array<Array<NodeIndex>> = []
|
|
3989
|
-
for (const startNode of
|
|
6364
|
+
for (const startNode of impl.nodes.keys()) {
|
|
3990
6365
|
if (!visited.has(startNode)) {
|
|
3991
6366
|
// DFS to find all nodes in this component
|
|
3992
6367
|
const component: Array<NodeIndex> = []
|
|
@@ -4051,6 +6426,7 @@ export const stronglyConnectedComponents = <N, E>(
|
|
|
4051
6426
|
throw new GraphError({ message: "Cannot find strongly connected components of undirected graph" })
|
|
4052
6427
|
}
|
|
4053
6428
|
|
|
6429
|
+
const impl = graphImpl(graph)
|
|
4054
6430
|
const visited = new Set<NodeIndex>()
|
|
4055
6431
|
const finishOrder: Array<NodeIndex> = []
|
|
4056
6432
|
// Iterate directly over node keys
|
|
@@ -4059,7 +6435,7 @@ export const stronglyConnectedComponents = <N, E>(
|
|
|
4059
6435
|
// Stack entry: [node, neighbors, neighborIndex, isFirstVisit]
|
|
4060
6436
|
type DfsStackEntry = [NodeIndex, Array<NodeIndex>, number, boolean]
|
|
4061
6437
|
|
|
4062
|
-
for (const startNode of
|
|
6438
|
+
for (const startNode of impl.nodes.keys()) {
|
|
4063
6439
|
if (visited.has(startNode)) {
|
|
4064
6440
|
continue
|
|
4065
6441
|
}
|
|
@@ -4121,10 +6497,10 @@ export const stronglyConnectedComponents = <N, E>(
|
|
|
4121
6497
|
scc.push(node)
|
|
4122
6498
|
|
|
4123
6499
|
// Use reverse adjacency (transpose graph)
|
|
4124
|
-
const reverseAdjacency =
|
|
6500
|
+
const reverseAdjacency = impl.reverseAdjacency.get(node)
|
|
4125
6501
|
if (reverseAdjacency !== undefined) {
|
|
4126
6502
|
for (const edgeIndex of reverseAdjacency) {
|
|
4127
|
-
const edge =
|
|
6503
|
+
const edge = impl.edges.get(edgeIndex)
|
|
4128
6504
|
if (edge !== undefined) {
|
|
4129
6505
|
const predecessor = edge.source
|
|
4130
6506
|
if (!visited.has(predecessor)) {
|
|
@@ -4178,6 +6554,53 @@ export interface PathResult<E> {
|
|
|
4178
6554
|
readonly costs: Array<E>
|
|
4179
6555
|
}
|
|
4180
6556
|
|
|
6557
|
+
interface MinHeapEntry {
|
|
6558
|
+
readonly node: NodeIndex
|
|
6559
|
+
readonly priority: number
|
|
6560
|
+
readonly sequence: number
|
|
6561
|
+
}
|
|
6562
|
+
|
|
6563
|
+
const minHeapLessThan = (self: MinHeapEntry, that: MinHeapEntry): boolean =>
|
|
6564
|
+
self.priority < that.priority || (self.priority === that.priority && self.sequence < that.sequence)
|
|
6565
|
+
|
|
6566
|
+
const minHeapPush = (heap: Array<MinHeapEntry>, entry: MinHeapEntry): void => {
|
|
6567
|
+
let index = heap.length
|
|
6568
|
+
heap.push(entry)
|
|
6569
|
+
while (index > 0) {
|
|
6570
|
+
const parent = (index - 1) >>> 1
|
|
6571
|
+
if (!minHeapLessThan(entry, heap[parent])) {
|
|
6572
|
+
break
|
|
6573
|
+
}
|
|
6574
|
+
heap[index] = heap[parent]
|
|
6575
|
+
index = parent
|
|
6576
|
+
}
|
|
6577
|
+
heap[index] = entry
|
|
6578
|
+
}
|
|
6579
|
+
|
|
6580
|
+
const minHeapPop = (heap: Array<MinHeapEntry>): MinHeapEntry | undefined => {
|
|
6581
|
+
const first = heap[0]
|
|
6582
|
+
const last = heap.pop()
|
|
6583
|
+
if (last === undefined || heap.length === 0) {
|
|
6584
|
+
return first
|
|
6585
|
+
}
|
|
6586
|
+
let index = 0
|
|
6587
|
+
while (true) {
|
|
6588
|
+
const left = index * 2 + 1
|
|
6589
|
+
if (left >= heap.length) {
|
|
6590
|
+
break
|
|
6591
|
+
}
|
|
6592
|
+
const right = left + 1
|
|
6593
|
+
const child = right < heap.length && minHeapLessThan(heap[right], heap[left]) ? right : left
|
|
6594
|
+
if (!minHeapLessThan(heap[child], last)) {
|
|
6595
|
+
break
|
|
6596
|
+
}
|
|
6597
|
+
heap[index] = heap[child]
|
|
6598
|
+
index = child
|
|
6599
|
+
}
|
|
6600
|
+
heap[index] = last
|
|
6601
|
+
return first
|
|
6602
|
+
}
|
|
6603
|
+
|
|
4181
6604
|
/**
|
|
4182
6605
|
* Configuration for finding a shortest path with Dijkstra's algorithm.
|
|
4183
6606
|
*
|
|
@@ -4189,12 +6612,13 @@ export interface PathResult<E> {
|
|
|
4189
6612
|
* **Details**
|
|
4190
6613
|
*
|
|
4191
6614
|
* Specifies the source and target node indices, plus a cost function that maps
|
|
4192
|
-
* each edge's data to a non-negative numeric weight.
|
|
6615
|
+
* each edge's data to a non-negative numeric weight. `Infinity` is allowed and
|
|
6616
|
+
* behaves like an impassable edge.
|
|
4193
6617
|
*
|
|
4194
6618
|
* **Gotchas**
|
|
4195
6619
|
*
|
|
4196
6620
|
* `dijkstra` throws a `GraphError` when either endpoint does not exist or when
|
|
4197
|
-
* the cost function returns a negative weight
|
|
6621
|
+
* the cost function returns a negative weight or `NaN`.
|
|
4198
6622
|
*
|
|
4199
6623
|
* @see {@link dijkstra} for the algorithm that consumes this configuration
|
|
4200
6624
|
* @see {@link AstarConfig} for heuristic shortest-path search
|
|
@@ -4209,31 +6633,16 @@ export interface DijkstraConfig<E> {
|
|
|
4209
6633
|
cost: (edgeData: E) => number
|
|
4210
6634
|
}
|
|
4211
6635
|
|
|
4212
|
-
const validateNonNegativeEdgeWeights = <N, E, T extends Kind = "directed">(
|
|
4213
|
-
graph: Graph<N, E, T> | MutableGraph<N, E, T>,
|
|
4214
|
-
cost: (edgeData: E) => number,
|
|
4215
|
-
algorithm: string
|
|
4216
|
-
): Map<EdgeIndex, number> => {
|
|
4217
|
-
const edgeWeights = new Map<EdgeIndex, number>()
|
|
4218
|
-
for (const [edgeIndex, edgeData] of graph.edges) {
|
|
4219
|
-
const weight = cost(edgeData.data)
|
|
4220
|
-
if (weight < 0 || Number.isNaN(weight)) {
|
|
4221
|
-
throw new GraphError({ message: `${algorithm} requires non-negative edge weights` })
|
|
4222
|
-
}
|
|
4223
|
-
edgeWeights.set(edgeIndex, weight)
|
|
4224
|
-
}
|
|
4225
|
-
return edgeWeights
|
|
4226
|
-
}
|
|
4227
|
-
|
|
4228
6636
|
/**
|
|
4229
6637
|
* Finds the shortest path from the configured source node to the target node
|
|
4230
6638
|
* using Dijkstra's algorithm.
|
|
4231
6639
|
*
|
|
4232
6640
|
* **Details**
|
|
4233
6641
|
*
|
|
4234
|
-
* Edge costs must be non-negative
|
|
4235
|
-
*
|
|
4236
|
-
*
|
|
6642
|
+
* Edge costs must be non-negative and not `NaN`. `Infinity` is allowed and
|
|
6643
|
+
* behaves like an impassable edge. Returns `Option.none()` when the target is
|
|
6644
|
+
* not reachable, and throws a `GraphError` when either endpoint is missing or an
|
|
6645
|
+
* edge cost is negative or `NaN`.
|
|
4237
6646
|
*
|
|
4238
6647
|
* **Example** (Finding shortest paths with Dijkstra)
|
|
4239
6648
|
*
|
|
@@ -4271,9 +6680,10 @@ export const dijkstra: {
|
|
|
4271
6680
|
*
|
|
4272
6681
|
* **Details**
|
|
4273
6682
|
*
|
|
4274
|
-
* Edge costs must be non-negative
|
|
4275
|
-
*
|
|
4276
|
-
*
|
|
6683
|
+
* Edge costs must be non-negative and not `NaN`. `Infinity` is allowed and
|
|
6684
|
+
* behaves like an impassable edge. Returns `Option.none()` when the target is
|
|
6685
|
+
* not reachable, and throws a `GraphError` when either endpoint is missing or an
|
|
6686
|
+
* edge cost is negative or `NaN`.
|
|
4277
6687
|
*
|
|
4278
6688
|
* **Example** (Finding shortest paths with Dijkstra)
|
|
4279
6689
|
*
|
|
@@ -4311,9 +6721,10 @@ export const dijkstra: {
|
|
|
4311
6721
|
*
|
|
4312
6722
|
* **Details**
|
|
4313
6723
|
*
|
|
4314
|
-
* Edge costs must be non-negative
|
|
4315
|
-
*
|
|
4316
|
-
*
|
|
6724
|
+
* Edge costs must be non-negative and not `NaN`. `Infinity` is allowed and
|
|
6725
|
+
* behaves like an impassable edge. Returns `Option.none()` when the target is
|
|
6726
|
+
* not reachable, and throws a `GraphError` when either endpoint is missing or an
|
|
6727
|
+
* edge cost is negative or `NaN`.
|
|
4317
6728
|
*
|
|
4318
6729
|
* **Example** (Finding shortest paths with Dijkstra)
|
|
4319
6730
|
*
|
|
@@ -4349,15 +6760,23 @@ export const dijkstra: {
|
|
|
4349
6760
|
graph: Graph<N, E, T> | MutableGraph<N, E, T>,
|
|
4350
6761
|
config: DijkstraConfig<E>
|
|
4351
6762
|
): Option.Option<PathResult<E>> => {
|
|
6763
|
+
const impl = graphImpl(graph)
|
|
4352
6764
|
// Validate that source and target nodes exist
|
|
4353
|
-
if (!
|
|
6765
|
+
if (!impl.nodes.has(config.source)) {
|
|
4354
6766
|
throw missingNode(config.source)
|
|
4355
6767
|
}
|
|
4356
|
-
if (!
|
|
6768
|
+
if (!impl.nodes.has(config.target)) {
|
|
4357
6769
|
throw missingNode(config.target)
|
|
4358
6770
|
}
|
|
4359
6771
|
|
|
4360
|
-
const edgeWeights =
|
|
6772
|
+
const edgeWeights = new Map<EdgeIndex, number>()
|
|
6773
|
+
for (const [edgeIndex, edgeData] of impl.edges) {
|
|
6774
|
+
const weight = config.cost(edgeData.data)
|
|
6775
|
+
if (Number.isNaN(weight) || weight < 0) {
|
|
6776
|
+
throw new GraphError({ message: "Dijkstra's algorithm requires non-negative edge weights" })
|
|
6777
|
+
}
|
|
6778
|
+
edgeWeights.set(edgeIndex, weight)
|
|
6779
|
+
}
|
|
4361
6780
|
|
|
4362
6781
|
// Early return if source equals target
|
|
4363
6782
|
if (config.source === config.target) {
|
|
@@ -4375,26 +6794,17 @@ export const dijkstra: {
|
|
|
4375
6794
|
|
|
4376
6795
|
// Initialize distances
|
|
4377
6796
|
// Iterate directly over node keys
|
|
4378
|
-
for (const node of
|
|
6797
|
+
for (const node of impl.nodes.keys()) {
|
|
4379
6798
|
distances.set(node, node === config.source ? 0 : Infinity)
|
|
4380
6799
|
previous.set(node, null)
|
|
4381
6800
|
}
|
|
4382
6801
|
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
]
|
|
6802
|
+
const priorityQueue: Array<MinHeapEntry> = []
|
|
6803
|
+
let sequence = 0
|
|
6804
|
+
minHeapPush(priorityQueue, { node: config.source, priority: 0, sequence: sequence++ })
|
|
4387
6805
|
|
|
4388
6806
|
while (priorityQueue.length > 0) {
|
|
4389
|
-
|
|
4390
|
-
let minIndex = 0
|
|
4391
|
-
for (let i = 1; i < priorityQueue.length; i++) {
|
|
4392
|
-
if (priorityQueue[i].distance < priorityQueue[minIndex].distance) {
|
|
4393
|
-
minIndex = i
|
|
4394
|
-
}
|
|
4395
|
-
}
|
|
4396
|
-
|
|
4397
|
-
const current = priorityQueue.splice(minIndex, 1)[0]
|
|
6807
|
+
const current = minHeapPop(priorityQueue)!
|
|
4398
6808
|
const currentNode = current.node
|
|
4399
6809
|
|
|
4400
6810
|
// Skip if already visited (can happen with duplicate entries)
|
|
@@ -4413,10 +6823,10 @@ export const dijkstra: {
|
|
|
4413
6823
|
const currentDistance = distances.get(currentNode)!
|
|
4414
6824
|
|
|
4415
6825
|
// Examine all outgoing edges
|
|
4416
|
-
const adjacencyList =
|
|
6826
|
+
const adjacencyList = impl.adjacency.get(currentNode)
|
|
4417
6827
|
if (adjacencyList !== undefined) {
|
|
4418
6828
|
for (const edgeIndex of adjacencyList) {
|
|
4419
|
-
const edge =
|
|
6829
|
+
const edge = impl.edges.get(edgeIndex)
|
|
4420
6830
|
if (edge !== undefined) {
|
|
4421
6831
|
const neighbor = getTraversableNeighbor(graph, currentNode, edge)
|
|
4422
6832
|
const cost = edgeWeights.get(edgeIndex)!
|
|
@@ -4431,7 +6841,7 @@ export const dijkstra: {
|
|
|
4431
6841
|
|
|
4432
6842
|
// Add to priority queue if not visited
|
|
4433
6843
|
if (!visited.has(neighbor)) {
|
|
4434
|
-
priorityQueue
|
|
6844
|
+
minHeapPush(priorityQueue, { node: neighbor, priority: newDistance, sequence: sequence++ })
|
|
4435
6845
|
}
|
|
4436
6846
|
}
|
|
4437
6847
|
}
|
|
@@ -4501,8 +6911,9 @@ export interface AllPairsResult<E> {
|
|
|
4501
6911
|
* **Details**
|
|
4502
6912
|
*
|
|
4503
6913
|
* Computes distances, reconstructed node paths, and edge-data paths for every
|
|
4504
|
-
* source and target pair in O(V^3) time. Negative edge weights are allowed,
|
|
4505
|
-
*
|
|
6914
|
+
* source and target pair in O(V^3) time. Negative edge weights are allowed, and
|
|
6915
|
+
* `Infinity` behaves like an impassable edge. A `GraphError` is thrown if any
|
|
6916
|
+
* edge weight is `NaN` or `-Infinity`, or if any negative cycle is detected.
|
|
4506
6917
|
*
|
|
4507
6918
|
* **Example** (Finding all-pairs shortest paths)
|
|
4508
6919
|
*
|
|
@@ -4534,8 +6945,9 @@ export const floydWarshall: {
|
|
|
4534
6945
|
* **Details**
|
|
4535
6946
|
*
|
|
4536
6947
|
* Computes distances, reconstructed node paths, and edge-data paths for every
|
|
4537
|
-
* source and target pair in O(V^3) time. Negative edge weights are allowed,
|
|
4538
|
-
*
|
|
6948
|
+
* source and target pair in O(V^3) time. Negative edge weights are allowed, and
|
|
6949
|
+
* `Infinity` behaves like an impassable edge. A `GraphError` is thrown if any
|
|
6950
|
+
* edge weight is `NaN` or `-Infinity`, or if any negative cycle is detected.
|
|
4539
6951
|
*
|
|
4540
6952
|
* **Example** (Finding all-pairs shortest paths)
|
|
4541
6953
|
*
|
|
@@ -4567,8 +6979,9 @@ export const floydWarshall: {
|
|
|
4567
6979
|
* **Details**
|
|
4568
6980
|
*
|
|
4569
6981
|
* Computes distances, reconstructed node paths, and edge-data paths for every
|
|
4570
|
-
* source and target pair in O(V^3) time. Negative edge weights are allowed,
|
|
4571
|
-
*
|
|
6982
|
+
* source and target pair in O(V^3) time. Negative edge weights are allowed, and
|
|
6983
|
+
* `Infinity` behaves like an impassable edge. A `GraphError` is thrown if any
|
|
6984
|
+
* edge weight is `NaN` or `-Infinity`, or if any negative cycle is detected.
|
|
4572
6985
|
*
|
|
4573
6986
|
* **Example** (Finding all-pairs shortest paths)
|
|
4574
6987
|
*
|
|
@@ -4600,13 +7013,14 @@ export const floydWarshall: {
|
|
|
4600
7013
|
graph: Graph<N, E, T> | MutableGraph<N, E, T>,
|
|
4601
7014
|
cost: (edgeData: E) => number
|
|
4602
7015
|
): AllPairsResult<E> => {
|
|
7016
|
+
const impl = graphImpl(graph)
|
|
4603
7017
|
// Get all nodes for Floyd-Warshall algorithm (needs array for nested iteration)
|
|
4604
|
-
const allNodes = Array.from(
|
|
7018
|
+
const allNodes = Array.from(impl.nodes.keys())
|
|
4605
7019
|
|
|
4606
7020
|
// Initialize distance matrix
|
|
4607
7021
|
const distances = new Map<NodeIndex, Map<NodeIndex, number>>()
|
|
4608
|
-
const next = new Map<NodeIndex, Map<NodeIndex, NodeIndex
|
|
4609
|
-
const edgeMatrix = new Map<NodeIndex, Map<NodeIndex, E
|
|
7022
|
+
const next = new Map<NodeIndex, Map<NodeIndex, NodeIndex>>()
|
|
7023
|
+
const edgeMatrix = new Map<NodeIndex, Map<NodeIndex, E>>()
|
|
4610
7024
|
|
|
4611
7025
|
// Initialize with infinity for all pairs
|
|
4612
7026
|
for (const i of allNodes) {
|
|
@@ -4616,14 +7030,15 @@ export const floydWarshall: {
|
|
|
4616
7030
|
|
|
4617
7031
|
for (const j of allNodes) {
|
|
4618
7032
|
distances.get(i)!.set(j, i === j ? 0 : Infinity)
|
|
4619
|
-
next.get(i)!.set(j, null)
|
|
4620
|
-
edgeMatrix.get(i)!.set(j, null)
|
|
4621
7033
|
}
|
|
4622
7034
|
}
|
|
4623
7035
|
|
|
4624
7036
|
// Set edge weights
|
|
4625
|
-
for (const [, edgeData] of
|
|
7037
|
+
for (const [, edgeData] of impl.edges) {
|
|
4626
7038
|
const weight = cost(edgeData.data)
|
|
7039
|
+
if (Number.isNaN(weight) || weight === -Infinity) {
|
|
7040
|
+
throw new GraphError({ message: "Floyd-Warshall algorithm does not support NaN or -Infinity edge weights" })
|
|
7041
|
+
}
|
|
4627
7042
|
const i = edgeData.source
|
|
4628
7043
|
const j = edgeData.target
|
|
4629
7044
|
|
|
@@ -4654,8 +7069,11 @@ export const floydWarshall: {
|
|
|
4654
7069
|
const distIJ = distances.get(i)!.get(j)!
|
|
4655
7070
|
|
|
4656
7071
|
if (distIK !== Infinity && distKJ !== Infinity && distIK + distKJ < distIJ) {
|
|
4657
|
-
|
|
4658
|
-
|
|
7072
|
+
const nextIK = next.get(i)!.get(k)
|
|
7073
|
+
if (nextIK !== undefined) {
|
|
7074
|
+
distances.get(i)!.set(j, distIK + distKJ)
|
|
7075
|
+
next.get(i)!.set(j, nextIK)
|
|
7076
|
+
}
|
|
4659
7077
|
}
|
|
4660
7078
|
}
|
|
4661
7079
|
}
|
|
@@ -4691,12 +7109,12 @@ export const floydWarshall: {
|
|
|
4691
7109
|
|
|
4692
7110
|
path.push(current)
|
|
4693
7111
|
while (current !== j) {
|
|
4694
|
-
const nextNode = next.get(current)!.get(j)
|
|
4695
|
-
if (nextNode ===
|
|
7112
|
+
const nextNode = next.get(current)!.get(j)
|
|
7113
|
+
if (nextNode === undefined) break
|
|
4696
7114
|
|
|
4697
|
-
const
|
|
4698
|
-
if (
|
|
4699
|
-
weights.push(
|
|
7115
|
+
const edgeRow = edgeMatrix.get(current)!
|
|
7116
|
+
if (edgeRow.has(nextNode)) {
|
|
7117
|
+
weights.push(edgeRow.get(nextNode) as E)
|
|
4700
7118
|
}
|
|
4701
7119
|
|
|
4702
7120
|
current = nextNode
|
|
@@ -4726,8 +7144,9 @@ export const floydWarshall: {
|
|
|
4726
7144
|
*
|
|
4727
7145
|
* **Details**
|
|
4728
7146
|
*
|
|
4729
|
-
* Specifies the source and target node indices, an edge-cost function
|
|
4730
|
-
*
|
|
7147
|
+
* Specifies the source and target node indices, an edge-cost function that maps
|
|
7148
|
+
* edge data to non-negative weights, and a heuristic that estimates the
|
|
7149
|
+
* remaining cost from a node to the target.
|
|
4731
7150
|
*
|
|
4732
7151
|
* @see {@link astar} for the algorithm that consumes this configuration
|
|
4733
7152
|
* @see {@link DijkstraConfig} for shortest paths without a heuristic
|
|
@@ -4749,10 +7168,11 @@ export interface AstarConfig<E, N> {
|
|
|
4749
7168
|
*
|
|
4750
7169
|
* **Details**
|
|
4751
7170
|
*
|
|
4752
|
-
* The edge-cost function must return non-negative weights
|
|
7171
|
+
* The edge-cost function must return non-negative weights and not `NaN`.
|
|
7172
|
+
* `Infinity` is allowed and behaves like an impassable edge. The heuristic
|
|
4753
7173
|
* should be consistent to preserve shortest-path guarantees. Returns
|
|
4754
7174
|
* `Option.none()` when the target is not reachable, and throws a `GraphError`
|
|
4755
|
-
* when either endpoint is missing or
|
|
7175
|
+
* when either endpoint is missing or an edge cost is negative or `NaN`.
|
|
4756
7176
|
*
|
|
4757
7177
|
* **Example** (Finding shortest paths with A-star)
|
|
4758
7178
|
*
|
|
@@ -4796,10 +7216,11 @@ export const astar: {
|
|
|
4796
7216
|
*
|
|
4797
7217
|
* **Details**
|
|
4798
7218
|
*
|
|
4799
|
-
* The edge-cost function must return non-negative weights
|
|
7219
|
+
* The edge-cost function must return non-negative weights and not `NaN`.
|
|
7220
|
+
* `Infinity` is allowed and behaves like an impassable edge. The heuristic
|
|
4800
7221
|
* should be consistent to preserve shortest-path guarantees. Returns
|
|
4801
7222
|
* `Option.none()` when the target is not reachable, and throws a `GraphError`
|
|
4802
|
-
* when either endpoint is missing or
|
|
7223
|
+
* when either endpoint is missing or an edge cost is negative or `NaN`.
|
|
4803
7224
|
*
|
|
4804
7225
|
* **Example** (Finding shortest paths with A-star)
|
|
4805
7226
|
*
|
|
@@ -4843,10 +7264,11 @@ export const astar: {
|
|
|
4843
7264
|
*
|
|
4844
7265
|
* **Details**
|
|
4845
7266
|
*
|
|
4846
|
-
* The edge-cost function must return non-negative weights
|
|
7267
|
+
* The edge-cost function must return non-negative weights and not `NaN`.
|
|
7268
|
+
* `Infinity` is allowed and behaves like an impassable edge. The heuristic
|
|
4847
7269
|
* should be consistent to preserve shortest-path guarantees. Returns
|
|
4848
7270
|
* `Option.none()` when the target is not reachable, and throws a `GraphError`
|
|
4849
|
-
* when either endpoint is missing or
|
|
7271
|
+
* when either endpoint is missing or an edge cost is negative or `NaN`.
|
|
4850
7272
|
*
|
|
4851
7273
|
* **Example** (Finding shortest paths with A-star)
|
|
4852
7274
|
*
|
|
@@ -4888,15 +7310,23 @@ export const astar: {
|
|
|
4888
7310
|
graph: Graph<N, E, T> | MutableGraph<N, E, T>,
|
|
4889
7311
|
config: AstarConfig<E, N>
|
|
4890
7312
|
): Option.Option<PathResult<E>> => {
|
|
7313
|
+
const impl = graphImpl(graph)
|
|
4891
7314
|
// Validate that source and target nodes exist
|
|
4892
|
-
if (!
|
|
7315
|
+
if (!impl.nodes.has(config.source)) {
|
|
4893
7316
|
throw missingNode(config.source)
|
|
4894
7317
|
}
|
|
4895
|
-
if (!
|
|
7318
|
+
if (!impl.nodes.has(config.target)) {
|
|
4896
7319
|
throw missingNode(config.target)
|
|
4897
7320
|
}
|
|
4898
7321
|
|
|
4899
|
-
const edgeWeights =
|
|
7322
|
+
const edgeWeights = new Map<EdgeIndex, number>()
|
|
7323
|
+
for (const [edgeIndex, edgeData] of impl.edges) {
|
|
7324
|
+
const weight = config.cost(edgeData.data)
|
|
7325
|
+
if (Number.isNaN(weight) || weight < 0) {
|
|
7326
|
+
throw new GraphError({ message: "A* algorithm requires non-negative edge weights" })
|
|
7327
|
+
}
|
|
7328
|
+
edgeWeights.set(edgeIndex, weight)
|
|
7329
|
+
}
|
|
4900
7330
|
|
|
4901
7331
|
// Early return if source equals target
|
|
4902
7332
|
if (config.source === config.target) {
|
|
@@ -4921,7 +7351,7 @@ export const astar: {
|
|
|
4921
7351
|
|
|
4922
7352
|
// Initialize scores
|
|
4923
7353
|
// Iterate directly over node keys
|
|
4924
|
-
for (const node of
|
|
7354
|
+
for (const node of impl.nodes.keys()) {
|
|
4925
7355
|
gScore.set(node, node === config.source ? 0 : Infinity)
|
|
4926
7356
|
fScore.set(node, Infinity)
|
|
4927
7357
|
previous.set(node, null)
|
|
@@ -4934,21 +7364,16 @@ export const astar: {
|
|
|
4934
7364
|
fScore.set(config.source, h)
|
|
4935
7365
|
}
|
|
4936
7366
|
|
|
4937
|
-
|
|
4938
|
-
|
|
4939
|
-
|
|
4940
|
-
|
|
7367
|
+
const openSet: Array<MinHeapEntry> = []
|
|
7368
|
+
let sequence = 0
|
|
7369
|
+
minHeapPush(openSet, {
|
|
7370
|
+
node: config.source,
|
|
7371
|
+
priority: fScore.get(config.source)!,
|
|
7372
|
+
sequence: sequence++
|
|
7373
|
+
})
|
|
4941
7374
|
|
|
4942
7375
|
while (openSet.length > 0) {
|
|
4943
|
-
|
|
4944
|
-
let minIndex = 0
|
|
4945
|
-
for (let i = 1; i < openSet.length; i++) {
|
|
4946
|
-
if (openSet[i].fScore < openSet[minIndex].fScore) {
|
|
4947
|
-
minIndex = i
|
|
4948
|
-
}
|
|
4949
|
-
}
|
|
4950
|
-
|
|
4951
|
-
const current = openSet.splice(minIndex, 1)[0]
|
|
7376
|
+
const current = minHeapPop(openSet)!
|
|
4952
7377
|
const currentNode = current.node
|
|
4953
7378
|
|
|
4954
7379
|
// Skip if already visited
|
|
@@ -4967,10 +7392,10 @@ export const astar: {
|
|
|
4967
7392
|
const currentGScore = gScore.get(currentNode)!
|
|
4968
7393
|
|
|
4969
7394
|
// Examine all outgoing edges
|
|
4970
|
-
const adjacencyList =
|
|
7395
|
+
const adjacencyList = impl.adjacency.get(currentNode)
|
|
4971
7396
|
if (adjacencyList !== undefined) {
|
|
4972
7397
|
for (const edgeIndex of adjacencyList) {
|
|
4973
|
-
const edge =
|
|
7398
|
+
const edge = impl.edges.get(edgeIndex)
|
|
4974
7399
|
if (edge !== undefined) {
|
|
4975
7400
|
const neighbor = getTraversableNeighbor(graph, currentNode, edge)
|
|
4976
7401
|
const weight = edgeWeights.get(edgeIndex)!
|
|
@@ -4993,7 +7418,7 @@ export const astar: {
|
|
|
4993
7418
|
|
|
4994
7419
|
// Add to open set if not visited
|
|
4995
7420
|
if (!visited.has(neighbor)) {
|
|
4996
|
-
openSet
|
|
7421
|
+
minHeapPush(openSet, { node: neighbor, priority: f, sequence: sequence++ })
|
|
4997
7422
|
}
|
|
4998
7423
|
}
|
|
4999
7424
|
}
|
|
@@ -5063,9 +7488,10 @@ export interface BellmanFordConfig<E> {
|
|
|
5063
7488
|
*
|
|
5064
7489
|
* **Details**
|
|
5065
7490
|
*
|
|
5066
|
-
* Negative edge weights are allowed
|
|
5067
|
-
*
|
|
5068
|
-
*
|
|
7491
|
+
* Negative edge weights are allowed, and `Infinity` behaves like an impassable
|
|
7492
|
+
* edge. Returns `Option.none()` when the target is unreachable or when a
|
|
7493
|
+
* negative cycle affects the path to the target. Throws a `GraphError` when
|
|
7494
|
+
* either endpoint is missing or an edge weight is `NaN` or `-Infinity`.
|
|
5069
7495
|
*
|
|
5070
7496
|
* **Example** (Finding shortest paths with Bellman-Ford)
|
|
5071
7497
|
*
|
|
@@ -5103,9 +7529,10 @@ export const bellmanFord: {
|
|
|
5103
7529
|
*
|
|
5104
7530
|
* **Details**
|
|
5105
7531
|
*
|
|
5106
|
-
* Negative edge weights are allowed
|
|
5107
|
-
*
|
|
5108
|
-
*
|
|
7532
|
+
* Negative edge weights are allowed, and `Infinity` behaves like an impassable
|
|
7533
|
+
* edge. Returns `Option.none()` when the target is unreachable or when a
|
|
7534
|
+
* negative cycle affects the path to the target. Throws a `GraphError` when
|
|
7535
|
+
* either endpoint is missing or an edge weight is `NaN` or `-Infinity`.
|
|
5109
7536
|
*
|
|
5110
7537
|
* **Example** (Finding shortest paths with Bellman-Ford)
|
|
5111
7538
|
*
|
|
@@ -5143,9 +7570,10 @@ export const bellmanFord: {
|
|
|
5143
7570
|
*
|
|
5144
7571
|
* **Details**
|
|
5145
7572
|
*
|
|
5146
|
-
* Negative edge weights are allowed
|
|
5147
|
-
*
|
|
5148
|
-
*
|
|
7573
|
+
* Negative edge weights are allowed, and `Infinity` behaves like an impassable
|
|
7574
|
+
* edge. Returns `Option.none()` when the target is unreachable or when a
|
|
7575
|
+
* negative cycle affects the path to the target. Throws a `GraphError` when
|
|
7576
|
+
* either endpoint is missing or an edge weight is `NaN` or `-Infinity`.
|
|
5149
7577
|
*
|
|
5150
7578
|
* **Example** (Finding shortest paths with Bellman-Ford)
|
|
5151
7579
|
*
|
|
@@ -5184,37 +7612,32 @@ export const bellmanFord: {
|
|
|
5184
7612
|
graph: Graph<N, E, T> | MutableGraph<N, E, T>,
|
|
5185
7613
|
config: BellmanFordConfig<E>
|
|
5186
7614
|
): Option.Option<PathResult<E>> => {
|
|
7615
|
+
const impl = graphImpl(graph)
|
|
5187
7616
|
// Validate that source and target nodes exist
|
|
5188
|
-
if (!
|
|
7617
|
+
if (!impl.nodes.has(config.source)) {
|
|
5189
7618
|
throw missingNode(config.source)
|
|
5190
7619
|
}
|
|
5191
|
-
if (!
|
|
7620
|
+
if (!impl.nodes.has(config.target)) {
|
|
5192
7621
|
throw missingNode(config.target)
|
|
5193
7622
|
}
|
|
5194
7623
|
|
|
5195
|
-
// Early return if source equals target
|
|
5196
|
-
if (config.source === config.target) {
|
|
5197
|
-
return Option.some({
|
|
5198
|
-
path: [config.source],
|
|
5199
|
-
distance: 0,
|
|
5200
|
-
costs: []
|
|
5201
|
-
})
|
|
5202
|
-
}
|
|
5203
|
-
|
|
5204
7624
|
// Initialize distances and predecessors
|
|
5205
7625
|
const distances = new Map<NodeIndex, number>()
|
|
5206
7626
|
const previous = new Map<NodeIndex, { node: NodeIndex; edgeData: E } | null>()
|
|
5207
7627
|
|
|
5208
7628
|
// Iterate directly over node keys
|
|
5209
|
-
for (const node of
|
|
7629
|
+
for (const node of impl.nodes.keys()) {
|
|
5210
7630
|
distances.set(node, node === config.source ? 0 : Infinity)
|
|
5211
7631
|
previous.set(node, null)
|
|
5212
7632
|
}
|
|
5213
7633
|
|
|
5214
7634
|
// Collect all edges for relaxation
|
|
5215
7635
|
const edges: Array<{ source: NodeIndex; target: NodeIndex; weight: number; edgeData: E }> = []
|
|
5216
|
-
for (const [, edgeData] of
|
|
7636
|
+
for (const [, edgeData] of impl.edges) {
|
|
5217
7637
|
const weight = config.cost(edgeData.data)
|
|
7638
|
+
if (Number.isNaN(weight) || weight === -Infinity) {
|
|
7639
|
+
throw new GraphError({ message: "Bellman-Ford algorithm does not support NaN or -Infinity edge weights" })
|
|
7640
|
+
}
|
|
5218
7641
|
edges.push({
|
|
5219
7642
|
source: edgeData.source,
|
|
5220
7643
|
target: edgeData.target,
|
|
@@ -5232,7 +7655,7 @@ export const bellmanFord: {
|
|
|
5232
7655
|
}
|
|
5233
7656
|
|
|
5234
7657
|
// Relax edges up to V-1 times
|
|
5235
|
-
const nodeCount =
|
|
7658
|
+
const nodeCount = impl.nodes.size
|
|
5236
7659
|
for (let i = 0; i < nodeCount - 1; i++) {
|
|
5237
7660
|
let hasUpdate = false
|
|
5238
7661
|
|
|
@@ -5427,7 +7850,7 @@ export class Walker<T, N> implements Iterable<[T, N]> {
|
|
|
5427
7850
|
visit: <U>(f: (index: T, data: N) => U) => Iterable<U>
|
|
5428
7851
|
) {
|
|
5429
7852
|
this.visit = visit
|
|
5430
|
-
this[Symbol.iterator] = visit((index, data) => [index, data] as [T, N])[Symbol.iterator]
|
|
7853
|
+
this[Symbol.iterator] = () => visit((index, data) => [index, data] as [T, N])[Symbol.iterator]()
|
|
5431
7854
|
}
|
|
5432
7855
|
}
|
|
5433
7856
|
|
|
@@ -5552,7 +7975,8 @@ export const entries = <T, N>(walker: Walker<T, N>): Iterable<[T, N]> =>
|
|
|
5552
7975
|
*
|
|
5553
7976
|
* `start` supplies the node indices where traversal begins. If it is omitted,
|
|
5554
7977
|
* the iterator is empty. `direction` chooses whether traversal follows
|
|
5555
|
-
* outgoing
|
|
7978
|
+
* outgoing edges, incoming edges, or ignores edge direction. `radius` limits
|
|
7979
|
+
* traversal by edge distance from the nearest start node.
|
|
5556
7980
|
*
|
|
5557
7981
|
* **Gotchas**
|
|
5558
7982
|
*
|
|
@@ -5568,7 +7992,8 @@ export const entries = <T, N>(walker: Walker<T, N>): Iterable<[T, N]> =>
|
|
|
5568
7992
|
*/
|
|
5569
7993
|
export interface SearchConfig {
|
|
5570
7994
|
readonly start?: Array<NodeIndex>
|
|
5571
|
-
readonly direction?:
|
|
7995
|
+
readonly direction?: TraversalDirection
|
|
7996
|
+
readonly radius?: number
|
|
5572
7997
|
}
|
|
5573
7998
|
|
|
5574
7999
|
/**
|
|
@@ -5578,7 +8003,8 @@ export interface SearchConfig {
|
|
|
5578
8003
|
* **Details**
|
|
5579
8004
|
*
|
|
5580
8005
|
* If no start nodes are supplied, the iterator is empty. The `direction` option
|
|
5581
|
-
* chooses whether to follow outgoing or incoming edges.
|
|
8006
|
+
* chooses whether to follow outgoing or incoming edges. The `radius` option
|
|
8007
|
+
* limits traversal by edge distance from the start nodes. Throws a `GraphError`
|
|
5582
8008
|
* if any configured start node does not exist.
|
|
5583
8009
|
*
|
|
5584
8010
|
* **Example** (Traversing depth-first)
|
|
@@ -5616,7 +8042,8 @@ export const dfs: {
|
|
|
5616
8042
|
* **Details**
|
|
5617
8043
|
*
|
|
5618
8044
|
* If no start nodes are supplied, the iterator is empty. The `direction` option
|
|
5619
|
-
* chooses whether to follow outgoing or incoming edges.
|
|
8045
|
+
* chooses whether to follow outgoing or incoming edges. The `radius` option
|
|
8046
|
+
* limits traversal by edge distance from the start nodes. Throws a `GraphError`
|
|
5620
8047
|
* if any configured start node does not exist.
|
|
5621
8048
|
*
|
|
5622
8049
|
* **Example** (Traversing depth-first)
|
|
@@ -5654,7 +8081,8 @@ export const dfs: {
|
|
|
5654
8081
|
* **Details**
|
|
5655
8082
|
*
|
|
5656
8083
|
* If no start nodes are supplied, the iterator is empty. The `direction` option
|
|
5657
|
-
* chooses whether to follow outgoing or incoming edges.
|
|
8084
|
+
* chooses whether to follow outgoing or incoming edges. The `radius` option
|
|
8085
|
+
* limits traversal by edge distance from the start nodes. Throws a `GraphError`
|
|
5658
8086
|
* if any configured start node does not exist.
|
|
5659
8087
|
*
|
|
5660
8088
|
* **Example** (Traversing depth-first)
|
|
@@ -5691,6 +8119,7 @@ export const dfs: {
|
|
|
5691
8119
|
): NodeWalker<N> => {
|
|
5692
8120
|
const start = config.start ?? []
|
|
5693
8121
|
const direction = config.direction ?? "outgoing"
|
|
8122
|
+
const radius = config.radius ?? Infinity
|
|
5694
8123
|
|
|
5695
8124
|
// Validate that all start nodes exist
|
|
5696
8125
|
for (const nodeIndex of start) {
|
|
@@ -5701,32 +8130,69 @@ export const dfs: {
|
|
|
5701
8130
|
|
|
5702
8131
|
return new Walker((f) => ({
|
|
5703
8132
|
[Symbol.iterator]: () => {
|
|
5704
|
-
const
|
|
5705
|
-
const
|
|
8133
|
+
const depths = radius === Infinity ? undefined : new Map<NodeIndex, number>()
|
|
8134
|
+
const stack: Array<readonly [NodeIndex, number]> = []
|
|
8135
|
+
const yielded = new Set<NodeIndex>()
|
|
8136
|
+
|
|
8137
|
+
if (depths === undefined) {
|
|
8138
|
+
for (const node of start) {
|
|
8139
|
+
stack.push([node, 0])
|
|
8140
|
+
}
|
|
8141
|
+
} else {
|
|
8142
|
+
const starts = new Set<NodeIndex>()
|
|
8143
|
+
for (let i = start.length - 1; i >= 0; i--) {
|
|
8144
|
+
const node = start[i]
|
|
8145
|
+
if (!starts.has(node)) {
|
|
8146
|
+
starts.add(node)
|
|
8147
|
+
stack.push([node, 0])
|
|
8148
|
+
depths.set(node, 0)
|
|
8149
|
+
}
|
|
8150
|
+
}
|
|
8151
|
+
stack.reverse()
|
|
8152
|
+
}
|
|
5706
8153
|
|
|
5707
8154
|
const nextMapped = () => {
|
|
5708
8155
|
while (stack.length > 0) {
|
|
5709
|
-
const current = stack.pop()!
|
|
8156
|
+
const [current, depth] = stack.pop()!
|
|
5710
8157
|
|
|
5711
|
-
if (
|
|
8158
|
+
if (depths === undefined) {
|
|
8159
|
+
if (yielded.has(current)) {
|
|
8160
|
+
continue
|
|
8161
|
+
}
|
|
8162
|
+
} else if (depths.get(current) !== depth) {
|
|
5712
8163
|
continue
|
|
5713
8164
|
}
|
|
5714
8165
|
|
|
5715
|
-
discovered.add(current)
|
|
5716
|
-
|
|
5717
8166
|
const nodeDataOption = getNode(graph, current)
|
|
5718
8167
|
if (Option.isNone(nodeDataOption)) {
|
|
5719
8168
|
continue
|
|
5720
8169
|
}
|
|
5721
8170
|
|
|
5722
|
-
|
|
5723
|
-
|
|
5724
|
-
|
|
5725
|
-
|
|
5726
|
-
|
|
8171
|
+
if (depth < radius) {
|
|
8172
|
+
const neighbors = getTraversalNeighbors(graph, current, direction)
|
|
8173
|
+
for (let i = neighbors.length - 1; i >= 0; i--) {
|
|
8174
|
+
const neighbor = neighbors[i]
|
|
8175
|
+
const nextDepth = depth + 1
|
|
8176
|
+
if (depths === undefined) {
|
|
8177
|
+
if (!yielded.has(neighbor)) {
|
|
8178
|
+
stack.push([neighbor, nextDepth])
|
|
8179
|
+
}
|
|
8180
|
+
continue
|
|
8181
|
+
}
|
|
8182
|
+
const neighborDepth = depths.get(neighbor)
|
|
8183
|
+
if (neighborDepth === undefined || nextDepth < neighborDepth) {
|
|
8184
|
+
depths.set(neighbor, nextDepth)
|
|
8185
|
+
stack.push([neighbor, nextDepth])
|
|
8186
|
+
}
|
|
5727
8187
|
}
|
|
5728
8188
|
}
|
|
5729
8189
|
|
|
8190
|
+
if (yielded.has(current)) {
|
|
8191
|
+
continue
|
|
8192
|
+
}
|
|
8193
|
+
|
|
8194
|
+
yielded.add(current)
|
|
8195
|
+
|
|
5730
8196
|
return { done: false, value: f(current, nodeDataOption.value) }
|
|
5731
8197
|
}
|
|
5732
8198
|
|
|
@@ -5745,7 +8211,8 @@ export const dfs: {
|
|
|
5745
8211
|
* **Details**
|
|
5746
8212
|
*
|
|
5747
8213
|
* If no start nodes are supplied, the iterator is empty. The `direction` option
|
|
5748
|
-
* chooses whether to follow outgoing or incoming edges.
|
|
8214
|
+
* chooses whether to follow outgoing or incoming edges. The `radius` option
|
|
8215
|
+
* limits traversal by edge distance from the start nodes. Throws a `GraphError`
|
|
5749
8216
|
* if any configured start node does not exist.
|
|
5750
8217
|
*
|
|
5751
8218
|
* **Example** (Traversing breadth-first)
|
|
@@ -5783,7 +8250,8 @@ export const bfs: {
|
|
|
5783
8250
|
* **Details**
|
|
5784
8251
|
*
|
|
5785
8252
|
* If no start nodes are supplied, the iterator is empty. The `direction` option
|
|
5786
|
-
* chooses whether to follow outgoing or incoming edges.
|
|
8253
|
+
* chooses whether to follow outgoing or incoming edges. The `radius` option
|
|
8254
|
+
* limits traversal by edge distance from the start nodes. Throws a `GraphError`
|
|
5787
8255
|
* if any configured start node does not exist.
|
|
5788
8256
|
*
|
|
5789
8257
|
* **Example** (Traversing breadth-first)
|
|
@@ -5821,7 +8289,8 @@ export const bfs: {
|
|
|
5821
8289
|
* **Details**
|
|
5822
8290
|
*
|
|
5823
8291
|
* If no start nodes are supplied, the iterator is empty. The `direction` option
|
|
5824
|
-
* chooses whether to follow outgoing or incoming edges.
|
|
8292
|
+
* chooses whether to follow outgoing or incoming edges. The `radius` option
|
|
8293
|
+
* limits traversal by edge distance from the start nodes. Throws a `GraphError`
|
|
5825
8294
|
* if any configured start node does not exist.
|
|
5826
8295
|
*
|
|
5827
8296
|
* **Example** (Traversing breadth-first)
|
|
@@ -5858,6 +8327,7 @@ export const bfs: {
|
|
|
5858
8327
|
): NodeWalker<N> => {
|
|
5859
8328
|
const start = config.start ?? []
|
|
5860
8329
|
const direction = config.direction ?? "outgoing"
|
|
8330
|
+
const radius = config.radius ?? Infinity
|
|
5861
8331
|
|
|
5862
8332
|
// Validate that all start nodes exist
|
|
5863
8333
|
for (const nodeIndex of start) {
|
|
@@ -5868,20 +8338,22 @@ export const bfs: {
|
|
|
5868
8338
|
|
|
5869
8339
|
return new Walker((f) => ({
|
|
5870
8340
|
[Symbol.iterator]: () => {
|
|
5871
|
-
const queue = [
|
|
8341
|
+
const queue: Array<readonly [NodeIndex, number]> = start.map((node) => [node, 0])
|
|
5872
8342
|
const discovered = new Set<NodeIndex>()
|
|
5873
8343
|
|
|
5874
8344
|
const nextMapped = () => {
|
|
5875
8345
|
while (queue.length > 0) {
|
|
5876
|
-
const current = queue.shift()!
|
|
8346
|
+
const [current, depth] = queue.shift()!
|
|
5877
8347
|
|
|
5878
8348
|
if (!discovered.has(current)) {
|
|
5879
8349
|
discovered.add(current)
|
|
5880
8350
|
|
|
5881
|
-
|
|
5882
|
-
|
|
5883
|
-
|
|
5884
|
-
|
|
8351
|
+
if (depth < radius) {
|
|
8352
|
+
const neighbors = getTraversalNeighbors(graph, current, direction)
|
|
8353
|
+
for (const neighbor of neighbors) {
|
|
8354
|
+
if (!discovered.has(neighbor)) {
|
|
8355
|
+
queue.push([neighbor, depth + 1])
|
|
8356
|
+
}
|
|
5885
8357
|
}
|
|
5886
8358
|
}
|
|
5887
8359
|
|
|
@@ -5889,7 +8361,7 @@ export const bfs: {
|
|
|
5889
8361
|
if (Option.isSome(nodeData)) {
|
|
5890
8362
|
return { done: false, value: f(current, nodeData.value) }
|
|
5891
8363
|
}
|
|
5892
|
-
|
|
8364
|
+
continue
|
|
5893
8365
|
}
|
|
5894
8366
|
}
|
|
5895
8367
|
|
|
@@ -6020,7 +8492,7 @@ export const topo: {
|
|
|
6020
8492
|
* @category iterators
|
|
6021
8493
|
* @since 3.18.0
|
|
6022
8494
|
*/
|
|
6023
|
-
(config?: TopoConfig): <N, E
|
|
8495
|
+
(config?: TopoConfig): <N, E>(graph: Graph<N, E, "directed"> | MutableGraph<N, E, "directed">) => NodeWalker<N>
|
|
6024
8496
|
/**
|
|
6025
8497
|
* Creates a new topological sort iterator with optional configuration.
|
|
6026
8498
|
*
|
|
@@ -6067,7 +8539,10 @@ export const topo: {
|
|
|
6067
8539
|
* @category iterators
|
|
6068
8540
|
* @since 3.18.0
|
|
6069
8541
|
*/
|
|
6070
|
-
<N, E
|
|
8542
|
+
<N, E>(
|
|
8543
|
+
graph: Graph<N, E, "directed"> | MutableGraph<N, E, "directed">,
|
|
8544
|
+
config?: TopoConfig
|
|
8545
|
+
): NodeWalker<N>
|
|
6071
8546
|
} = dual((args) => isGraph(args[0]), <N, E, T extends Kind = "directed">(
|
|
6072
8547
|
graph: Graph<N, E, T> | MutableGraph<N, E, T>,
|
|
6073
8548
|
config: TopoConfig = {}
|
|
@@ -6092,19 +8567,20 @@ export const topo: {
|
|
|
6092
8567
|
|
|
6093
8568
|
return new Walker((f) => ({
|
|
6094
8569
|
[Symbol.iterator]: () => {
|
|
8570
|
+
const impl = graphImpl(graph)
|
|
6095
8571
|
const inDegree = new Map<NodeIndex, number>()
|
|
6096
8572
|
const remaining = new Set<NodeIndex>()
|
|
6097
8573
|
const initialSet = new Set(initials)
|
|
6098
8574
|
const queue = [...initials]
|
|
6099
8575
|
|
|
6100
8576
|
// Initialize in-degree counts
|
|
6101
|
-
for (const [nodeIndex] of
|
|
8577
|
+
for (const [nodeIndex] of impl.nodes) {
|
|
6102
8578
|
inDegree.set(nodeIndex, 0)
|
|
6103
8579
|
remaining.add(nodeIndex)
|
|
6104
8580
|
}
|
|
6105
8581
|
|
|
6106
8582
|
// Calculate in-degrees
|
|
6107
|
-
for (const [, edgeData] of
|
|
8583
|
+
for (const [, edgeData] of impl.edges) {
|
|
6108
8584
|
const currentInDegree = inDegree.get(edgeData.target) || 0
|
|
6109
8585
|
inDegree.set(edgeData.target, currentInDegree + 1)
|
|
6110
8586
|
}
|
|
@@ -6152,10 +8628,14 @@ export const topo: {
|
|
|
6152
8628
|
if (Option.isSome(nodeData)) {
|
|
6153
8629
|
return { done: false, value: f(current, nodeData.value) }
|
|
6154
8630
|
}
|
|
6155
|
-
|
|
8631
|
+
continue
|
|
6156
8632
|
}
|
|
6157
8633
|
}
|
|
6158
8634
|
|
|
8635
|
+
if (remaining.size > 0) {
|
|
8636
|
+
throw new GraphError({ message: "Cannot perform topological sort on cyclic graph" })
|
|
8637
|
+
}
|
|
8638
|
+
|
|
6159
8639
|
return { done: true, value: undefined } as const
|
|
6160
8640
|
}
|
|
6161
8641
|
|
|
@@ -6172,7 +8652,14 @@ export const topo: {
|
|
|
6172
8652
|
*
|
|
6173
8653
|
* Nodes are emitted after their reachable descendants have been processed. If
|
|
6174
8654
|
* no start nodes are supplied, the iterator is empty. The `direction` option
|
|
6175
|
-
* chooses whether to follow outgoing or incoming edges.
|
|
8655
|
+
* chooses whether to follow outgoing or incoming edges. The `radius` option
|
|
8656
|
+
* limits traversal by edge distance from the start nodes.
|
|
8657
|
+
*
|
|
8658
|
+
* **Gotchas**
|
|
8659
|
+
*
|
|
8660
|
+
* With a finite `radius`, iteration first performs a bounded breadth-first
|
|
8661
|
+
* traversal to determine shortest-distance membership before emitting nodes in
|
|
8662
|
+
* postorder.
|
|
6176
8663
|
*
|
|
6177
8664
|
* **Example** (Traversing in postorder)
|
|
6178
8665
|
*
|
|
@@ -6206,7 +8693,14 @@ export const dfsPostOrder: {
|
|
|
6206
8693
|
*
|
|
6207
8694
|
* Nodes are emitted after their reachable descendants have been processed. If
|
|
6208
8695
|
* no start nodes are supplied, the iterator is empty. The `direction` option
|
|
6209
|
-
* chooses whether to follow outgoing or incoming edges.
|
|
8696
|
+
* chooses whether to follow outgoing or incoming edges. The `radius` option
|
|
8697
|
+
* limits traversal by edge distance from the start nodes.
|
|
8698
|
+
*
|
|
8699
|
+
* **Gotchas**
|
|
8700
|
+
*
|
|
8701
|
+
* With a finite `radius`, iteration first performs a bounded breadth-first
|
|
8702
|
+
* traversal to determine shortest-distance membership before emitting nodes in
|
|
8703
|
+
* postorder.
|
|
6210
8704
|
*
|
|
6211
8705
|
* **Example** (Traversing in postorder)
|
|
6212
8706
|
*
|
|
@@ -6240,7 +8734,14 @@ export const dfsPostOrder: {
|
|
|
6240
8734
|
*
|
|
6241
8735
|
* Nodes are emitted after their reachable descendants have been processed. If
|
|
6242
8736
|
* no start nodes are supplied, the iterator is empty. The `direction` option
|
|
6243
|
-
* chooses whether to follow outgoing or incoming edges.
|
|
8737
|
+
* chooses whether to follow outgoing or incoming edges. The `radius` option
|
|
8738
|
+
* limits traversal by edge distance from the start nodes.
|
|
8739
|
+
*
|
|
8740
|
+
* **Gotchas**
|
|
8741
|
+
*
|
|
8742
|
+
* With a finite `radius`, iteration first performs a bounded breadth-first
|
|
8743
|
+
* traversal to determine shortest-distance membership before emitting nodes in
|
|
8744
|
+
* postorder.
|
|
6244
8745
|
*
|
|
6245
8746
|
* **Example** (Traversing in postorder)
|
|
6246
8747
|
*
|
|
@@ -6272,6 +8773,7 @@ export const dfsPostOrder: {
|
|
|
6272
8773
|
): NodeWalker<N> => {
|
|
6273
8774
|
const start = config.start ?? []
|
|
6274
8775
|
const direction = config.direction ?? "outgoing"
|
|
8776
|
+
const radius = config.radius ?? Infinity
|
|
6275
8777
|
|
|
6276
8778
|
// Validate that all start nodes exist
|
|
6277
8779
|
for (const nodeIndex of start) {
|
|
@@ -6282,6 +8784,9 @@ export const dfsPostOrder: {
|
|
|
6282
8784
|
|
|
6283
8785
|
return new Walker((f) => ({
|
|
6284
8786
|
[Symbol.iterator]: () => {
|
|
8787
|
+
const reached = radius === Infinity
|
|
8788
|
+
? undefined
|
|
8789
|
+
: new Set(indices(bfs(graph, { start, direction, radius })))
|
|
6285
8790
|
const stack: Array<{ node: NodeIndex; visitedChildren: boolean }> = []
|
|
6286
8791
|
const discovered = new Set<NodeIndex>()
|
|
6287
8792
|
const finished = new Set<NodeIndex>()
|
|
@@ -6306,7 +8811,11 @@ export const dfsPostOrder: {
|
|
|
6306
8811
|
|
|
6307
8812
|
for (let i = neighbors.length - 1; i >= 0; i--) {
|
|
6308
8813
|
const neighbor = neighbors[i]
|
|
6309
|
-
if (
|
|
8814
|
+
if (
|
|
8815
|
+
(reached === undefined || reached.has(neighbor)) &&
|
|
8816
|
+
!discovered.has(neighbor) &&
|
|
8817
|
+
!finished.has(neighbor)
|
|
8818
|
+
) {
|
|
6310
8819
|
stack.push({ node: neighbor, visitedChildren: false })
|
|
6311
8820
|
}
|
|
6312
8821
|
}
|
|
@@ -6320,7 +8829,7 @@ export const dfsPostOrder: {
|
|
|
6320
8829
|
if (Option.isSome(nodeData)) {
|
|
6321
8830
|
return { done: false, value: f(nodeToEmit, nodeData.value) }
|
|
6322
8831
|
}
|
|
6323
|
-
|
|
8832
|
+
continue
|
|
6324
8833
|
}
|
|
6325
8834
|
}
|
|
6326
8835
|
}
|
|
@@ -6365,7 +8874,7 @@ export const nodes = <N, E, T extends Kind = "directed">(
|
|
|
6365
8874
|
): NodeWalker<N> =>
|
|
6366
8875
|
new Walker((f) => ({
|
|
6367
8876
|
[Symbol.iterator]() {
|
|
6368
|
-
const nodeMap = graph.nodes
|
|
8877
|
+
const nodeMap = graphImpl(graph).nodes
|
|
6369
8878
|
const iterator = nodeMap.entries()
|
|
6370
8879
|
|
|
6371
8880
|
return {
|
|
@@ -6414,7 +8923,7 @@ export const edges = <N, E, T extends Kind = "directed">(
|
|
|
6414
8923
|
): EdgeWalker<E> =>
|
|
6415
8924
|
new Walker((f) => ({
|
|
6416
8925
|
[Symbol.iterator]() {
|
|
6417
|
-
const edgeMap = graph.edges
|
|
8926
|
+
const edgeMap = graphImpl(graph).edges
|
|
6418
8927
|
const iterator = edgeMap.entries()
|
|
6419
8928
|
|
|
6420
8929
|
return {
|
|
@@ -6585,10 +9094,11 @@ export const externals: {
|
|
|
6585
9094
|
|
|
6586
9095
|
return new Walker((f) => ({
|
|
6587
9096
|
[Symbol.iterator]: () => {
|
|
6588
|
-
const
|
|
9097
|
+
const impl = graphImpl(graph)
|
|
9098
|
+
const nodeMap = impl.nodes
|
|
6589
9099
|
const adjacencyMap = direction === "incoming"
|
|
6590
|
-
?
|
|
6591
|
-
:
|
|
9100
|
+
? impl.reverseAdjacency
|
|
9101
|
+
: impl.adjacency
|
|
6592
9102
|
|
|
6593
9103
|
const nodeIterator = nodeMap.entries()
|
|
6594
9104
|
|