How can I limit the depth of recursion in the function?
3 Answers
Here's a simple function that inserts '...' when an object has been seen before, preventing infinite recursion.
function safeStringify (value) {
const seen = new Set()
return JSON.stringify(value, (k, v) => {
if (seen.has(v)) { return '...' }
if (typeof v === 'object') { seen.add(v) }
return v
})
}
Comments
When creating a recursive function, you can always pass a so called accumulative parameter. Simply pass an extra numeric parameter that you increment each time you enter a new level of recursion (i.e. increment your parameter exactly once each recursive iteration) and pass it through.
You can use this technique for a variety of things, as well as keeping track of the depth of recursion. Simply check the value of the parameter at the start of your function, and return when it is equal to the maximum depth that you wish to traverse into.
Example
This example shows a variable called depthLevel that signifies the level of depth in a tree for the current node. The maxDepthLevel constant should be defined somewhere. This Depth First algorithm will not traverse deeper than maxDepthLevel. Notice how the depthLevel is increased by 1 for each level of recursion, making it an accumulative parameter.
function depthFirst(var node, var depthLevel) {
if(depthLevel > maxDepthLevel) {
return;
}
//do logic for this node here
var childrenOfThisNode = node.getChildren();
foreach(var child in childrenOfThisNode) {
depthFirst(child, depthLevel + 1)
}
}
2 Comments
const a = {
a: 3,
b: {
b1: "sg",
b2: "sif",
b3: {
bc1: 3,
bc2: null,
},
},
c: {
c1: 6,
c2: false,
},
};
a.b.b3.bc3 = a;
const p = document.querySelector("p");
class StringifyTracker {
topLevelValue = null;
seend = new WeakSet();
seenr = new WeakSet();
maxDepth = 0;
constructor(maxDepth) {
if (maxDepth != null) {
this.maxDepth = maxDepth;
}
}
depthWithin(obj, subobj, level, found) {
let ret = {
level,
found: found || obj === subobj,
};
if (!ret.found && !this.seend.has(this.topLevelValue)) {
for (const key in obj) {
if (
!found &&
obj[key] &&
typeof obj[key] == "object" &&
!this.seend.has(obj[key])
) {
this.seend.add(obj[key]);
const dw = this.depthWithin(obj[key], subobj, level + 1, found);
if (dw.found) {
ret = dw;
break;
}
}
}
}
return ret;
}
replacer(key, value) {
let ret = value;
if (!this.topLevelValue) {
this.topLevelValue = value;
}
if (!(value === null || value === undefined) && typeof value === "object") {
if (this.seenr.has(value)) {
ret = "[Circular Ref]";
} else {
this.seend = new WeakSet();
const depth = this.depthWithin(
this.topLevelValue,
value,
0,
false
).level;
console.log("depth of " + key + " = " + depth);
if (depth > this.maxDepth) {
ret = "[Object]";
}
this.seenr.add(value);
}
}
return ret;
}
}
const st = new StringifyTracker(1);
p.innerHTML = JSON.stringify(a, (key, value) => st.replacer(key, value), 4)
.replaceAll("\n", "</p><p>")
.replaceAll(" ", " ");