Fundamental Traversal Patterns

Just a guy who loves to write code and watch anime.
Search for a command to run...

Just a guy who loves to write code and watch anime.
No comments yet. Be the first to comment.
Qualities that make outstanding builders.

Bipedal vs quadrupedal: how many legs This is about the number of legs the creature walks on. Bipedal. Two legs. Humans, ostriches, T-rex, kangaroos, most fantasy humanoids. Quadrupedal. Four legs. Do

Intro You hear "lerp" and "smoothstep" everywhere in game dev. They sound like math jargon. They're not. Both are small tools that do the same job: smoothly move from one value to another. The problem

What is Kinematics Kinematics is the math field. It's the study of how things move without worrying about forces (which would be dynamics). FK and IK are the two branches: forward kinematics and inver

Intro Textures are usually the biggest cost in a 3D scene. Memory, bandwidth, and load time all get eaten by them. Resizing them is the obvious lever. There's more. This post is about the less obvious

Recursion can be tricky. There are four fundamental patterns good to know.
To be honest, I just came up with the names myself lmao, but these patterns are pretty common.
function findInTree(node, target) {
// Common mistake: forgetting to return the result
if (node.value === target) {
return node; // Found it!
}
if (node.children) {
for (const child of node.children) {
// Common mistake: not capturing the return value
const found = findInTree(child, target); // Important!
if (found) return found; // Pass it up!
}
}
return null; // Nothing found in this branch
}
When calling findInTree(child, target) again, you can't return it then. Otherwise what would happen is that the function would stop after the first child since it would return right away even though you didn't find the target.
function multiplyAllValues(node, multiplier) {
// Modify current node
node.value *= multiplier;
// Recurse on children if they exist
if (node.children) {
node.children.forEach((child) => {
multiplyAllValues(child, multiplier);
});
}
// No return needed if just modifying
}
Here we modify the value in place. Because objects are passed by reference, the changes will be reflected in the original object. When we say "reference" we mean that the object is not copied. The same object in the function points to the same memory location as the original object. That's why when you modify what e.g. field of obj points to, it will be reflected in the memory location of obj.
function getAllLeafNodes(node, results = []) {
// Base case: it's a leaf
if (!node.children || node.children.length === 0) {
results.push(node);
} else {
// Recurse on all children
node.children.forEach((child) => {
getAllLeafNodes(child, results);
});
}
return results;
}
Here we're modifying the results array as we go. Arrays are objects under the hood, so they are passed by reference. That means that the changes will be reflected in the original array (the same one throughout the recursion).
It's important not to forget to pass the results array when calling the function recursively. And make sure to return the results array at the end. Otherwise nothing will be returned.
function findNodeAndPath(
nodes: Array<Node>,
targetId: string,
path: Array<Node> = []
): Array<Node> | null {
for (const node of nodes) {
path.push(node);
if (node.id === targetId) {
return path;
}
if ("children" in node && node.children.length > 0) {
const found = findNodeAndPath(node.children, targetId, path);
if (found) return found;
}
path.pop();
}
return null;
}
Here we're keeping track of the path to the target node as we go. When we return the path, the last element is the target node. The first element would be the upper most parent node.