feat: add graph set operators#6394
Conversation
🦋 Changeset detectedLatest commit: a58b1b7 The changes in this PR will be included in the next version bump. This PR includes changesets to release 27 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
I closed the split PRs again (I just wanted them to review the seperate chunks independently). |
| const emptyLike = <N, E, T extends Kind>( | ||
| graph: Graph<N, E, T>, | ||
| mutate: (mutable: MutableGraph<N, E, T>) => void | ||
| ): Graph<N, E, T> => { | ||
| const mutable = beginMutation(graph) | ||
|
|
||
| mutable.nodes.clear() | ||
| mutable.edges.clear() | ||
| mutable.adjacency.clear() | ||
| mutable.reverseAdjacency.clear() | ||
| mutable.nextNodeIndex = 0 | ||
| mutable.nextEdgeIndex = 0 | ||
| mutable.acyclic = Option.some(true) | ||
|
|
||
| mutate(mutable) | ||
|
|
||
| return endMutation(mutable) | ||
| } |
There was a problem hiding this comment.
This is odd and adds unnecessary overhead and should not be needed.
There was a problem hiding this comment.
I'd probably do something like this:
const make =
<T extends Kind>(type: T) =>
<N, E>(
mutate?: (mutable: MutableGraph<N, E, T>) => void
): Graph<N, E, T> => {
const graph: Mutable<Graph<N, E, T>> = Object.create(ProtoGraph)
graph.type = type
graph.nodes = new Map()
graph.edges = new Map()
graph.adjacency = new Map()
graph.reverseAdjacency = new Map()
graph.nextNodeIndex = 0
graph.nextEdgeIndex = 0
graph.acyclic = Option.some(true)
graph.mutable = false
if (mutate !== undefined) {
const mutable = beginMutation(graph)
mutate(mutable)
return endMutation(mutable)
}
return graph
}
export const directed: <N, E>(
mutate?: (mutable: MutableDirectedGraph<N, E>) => void
) => DirectedGraph<N, E> = make("directed")
export const undirected: <N, E>(
mutate?: (mutable: MutableUndirectedGraph<N, E>) => void
) => UndirectedGraph<N, E> = make("undirected")And then instead of using emptyLike you'd call make(self.type)(mutate)
| /** @internal */ | ||
| type NodeMaps<N> = { | ||
| readonly byId: Map<string, { readonly index: NodeIndex; readonly data: N }> | ||
| readonly byIndex: Map<NodeIndex, string> | ||
| } | ||
|
|
||
| /** @internal */ | ||
| type EdgeIdentity<E> = { readonly sourceId: string; readonly targetId: string; readonly data: E } | ||
|
|
||
| /** @internal */ | ||
| const buildNodeMaps = <N, E, T extends Kind>(graph: Graph<N, E, T>, nodeId: (node: N) => string): NodeMaps<N> => { | ||
| const byId = new Map<string, { readonly index: NodeIndex; readonly data: N }>() | ||
| const byIndex = new Map<NodeIndex, string>() | ||
|
|
||
| for (const [index, data] of graph.nodes) { | ||
| const id = nodeId(data) | ||
| byId.set(id, { index, data }) | ||
| byIndex.set(index, id) | ||
| } | ||
|
|
||
| return { byId, byIndex } | ||
| } | ||
|
|
||
| /** @internal */ | ||
| const edgeKey = (type: Kind, sourceId: string, targetId: string): string => | ||
| type === "undirected" && sourceId > targetId ? `${targetId}\0${sourceId}` : `${sourceId}\0${targetId}` |
There was a problem hiding this comment.
Still not sure about the identity handling tbh.
There was a problem hiding this comment.
I'll have to think about this for a bit.
There was a problem hiding this comment.
I still think there's something here with Equivalence & Hash
Type
Description
Have added 4 basic and 3 advanced set operators to the Graph module to be able to build and explore for interesting structures. These include:
Graph.compose-merges two graphs by node identity, combining nodes and edges from both. Overlapping data from that wins.
Graph.intersection-keeps only nodes and edges present in both graphs.
Graph.difference-keeps all nodes from self, but removes edges that also exist in that.
Graph.symmetricDifference-keeps edges that exist in exactly one graph, removing shared edges.
Graph.complement-keeps the same nodes, but adds every missing non-self edge.
Graph.neighborhood-builds an induced subgraph around a node within a radius.
Graph.sum-creates a disjoint union of two graphs without merging equal nodes.
Most of the implementation was done similar to
NetworkXwhen it came to making decisions around whenselforthatwould win on conflict and the main decision on node equality is left to the implementor where they need to pass a nodeId callback to be able to identify each node.One additional refactor (separate commit) was the additional logic to
Graph.bfsandGraph.dfsto allow for amaxDepth. This was important to be able to reuse the BFS in theneighbordhoodop and create a custom version like I did in my utils experiment.Some fun and exciting things can be done with this functionality.
For Reviewing
To make things easier to review I've broken up the three main parts so they can be reviewed separately and left off the type tests till last. Any feedback or requests I will initial add as commits on top of the current tree and then rebase together to clean up once approved
Related
Came up as a request in Slack with a bit of back and forth discussion with @fubhy. I then spend a couple of days building similar functions in my own repo to be sure of the api as well as test out their implementation with some real examples.