mirror of
https://github.com/mongodb/mongo.git
synced 2024-12-01 01:21:03 +01:00
239f4fac25
Also adds a helper function for running a function inside of a transaction and automatically retrying until it either succeeds or the server returns a non-WriteConflict error response.
118 lines
4.5 KiB
JavaScript
118 lines
4.5 KiB
JavaScript
/**
|
|
* A class representing a directed graph.
|
|
*/
|
|
function Graph() {
|
|
'use strict';
|
|
|
|
if (!(this instanceof Graph)) {
|
|
return new Graph();
|
|
}
|
|
|
|
const nodes = new Set();
|
|
const adjList = new Map();
|
|
|
|
this.addEdge = function addEdge(fromNode, toNode) {
|
|
nodes.add(fromNode);
|
|
nodes.add(toNode);
|
|
|
|
let neighbors = adjList.get(fromNode);
|
|
if (!(neighbors instanceof Set)) {
|
|
neighbors = new Set();
|
|
adjList.put(fromNode, neighbors);
|
|
}
|
|
neighbors.add(toNode);
|
|
};
|
|
|
|
const State = {
|
|
kNotYetVisited: Symbol('not yet visited'),
|
|
kVisitingInProgress: Symbol('visiting in progress'),
|
|
kAlreadyVisited: Symbol('already visited'),
|
|
};
|
|
|
|
/**
|
|
* If a cycle exists in this graph, then this function returns an array of the nodes comprising
|
|
* the cycle. The returned array is guaranteed to contain at least two elements, where the first
|
|
* and last elements refer to the same node.
|
|
*
|
|
* If a cycle doesn't exist in this graph, then this function returns an empty array.
|
|
*
|
|
* The algorithm implemented here for detecting whether a cycle exists in the graph is adapted
|
|
* from the algorithm for using a depth-first search to determine whether the graph permits a
|
|
* topological sort. Note that a topological ordering is possible if and only if the graph is
|
|
* acyclic.
|
|
*
|
|
* References:
|
|
* - https://en.wikipedia.org/wiki/Topological_sorting#Depth-first_search
|
|
* - http://www.cs.cornell.edu/courses/cs2112/2012sp/lectures/lec24/lec24-12sp.html
|
|
*/
|
|
this.findCycle = function findCycle() {
|
|
const state = new Map();
|
|
for (let node of nodes) {
|
|
state.put(node, State.kNotYetVisited);
|
|
}
|
|
|
|
function doDepthFirstSearch(node) {
|
|
{
|
|
const nodeState = state.get(node);
|
|
if (nodeState !== State.kNotYetVisited) {
|
|
throw new Error('Found node ' + tojsononeline(node) +
|
|
' unexpectedly in state ' + nodeState.toString());
|
|
}
|
|
}
|
|
|
|
state.put(node, State.kVisitingInProgress);
|
|
|
|
const neighbors = adjList.get(node) || [];
|
|
for (let otherNode of neighbors) {
|
|
const otherNodeState = state.get(otherNode);
|
|
if (otherNodeState === State.kAlreadyVisited) {
|
|
// We've already explored all neighbors of 'otherNode'. Since we are currently
|
|
// in the process of visiting 'node', it must be the case there doesn't exist a
|
|
// path from 'otherNode' to 'node'. There is therefore no cycle containing both
|
|
// 'node' and 'otherNode'.
|
|
continue;
|
|
}
|
|
|
|
if (otherNodeState === State.kVisitingInProgress) {
|
|
// We're currently in the process of exploring all neighbors of 'otherNode'.
|
|
// Since we are currently in the process of visiting 'node', it must be the case
|
|
// that there exists a path from 'otherNode' to 'node'. There is therefore a
|
|
// cycle containing both 'node' and 'otherNode'.
|
|
return [node, otherNode];
|
|
}
|
|
|
|
const result = doDepthFirstSearch(otherNode);
|
|
if (result.length > 1) {
|
|
// A cycle has been detected during the recursive call to doDepthFirstSearch().
|
|
// Unless we've already closed the loop, the (node, otherNode) edge must be part
|
|
// of it. Note that we use friendlyEqual() to match the definition of sameness
|
|
// as the mongo shell's Map type.
|
|
if (!friendlyEqual(result[0], result[result.length - 1])) {
|
|
result.unshift(node);
|
|
}
|
|
return result;
|
|
}
|
|
}
|
|
|
|
state.put(node, State.kAlreadyVisited);
|
|
return [];
|
|
}
|
|
|
|
for (let node of nodes) {
|
|
if (state.get(node) === State.kAlreadyVisited) {
|
|
// We've already explored all paths from 'node' by starting from one of its
|
|
// ancestors and didn't find a cycle. There is therefore no cycle involving 'node'
|
|
// so we move onto another node.
|
|
continue;
|
|
}
|
|
|
|
const result = doDepthFirstSearch(node);
|
|
if (result.length > 0) {
|
|
return result;
|
|
}
|
|
}
|
|
|
|
return [];
|
|
};
|
|
}
|