letcompany={sales:[{name:'John',salary:1000,},{name:'Alice',salary:1600,},],development:{sites:[{name:'Peter',salary:2000,},{name:'Alex',salary:1800,},],internals:[{name:'Jack',salary:1300,},],},};constaddEmployeeSalary=(sum,employee)=>sum+employee.salary;constsumEmployeeSalaries=(employees,total)=>employees.reduce(addEmployeeSalary,total);// Body recursive solutionfunctionsumSalaries(department,total=0){// base case - works on leaf nodes; a list of employeesif(Array.isArray(department))returnsumEmployeeSalaries(department,total);// recursive case - edge nodes lead to sub departmentsfor(letsubdepofObject.values(department)){total=sumSalaries(subdep,total);// recursively call for subdepartments, sum the results}returntotal;}console.log(sumSalaries(company));// 7700
Convert to a tail recursive solution
// prime recursionconstsumSalaries=(department)=>sum([department],0,0);functionsum(todo,i,total){// base case - nothing left to doif(i>=todo.length)returntotal;// recursive caseconstcurrent=todo[i];if(Array.isArray(current)){// works on a leaf node; a list of employeestotal=sumEmployeeSalaries(current,total);}else{// edge node leads to sub departmentstodo.push(...Object.values(current));}returnsum(todo,i+1,total);}
Introduce a one-shot loop around the function body
functionsum(todo,i,total){while(true){// base case - nothing left to doif(i>=todo.length)returntotal;// recursive caseconstcurrent=todo[i];if(Array.isArray(current)){// works on a leaf node; a list of employeestotal=sumEmployeeSalaries(current,total);}else{// edge node leads to sub departmentstodo.push(...Object.values(current));}returnsum(todo,i+1,total);break;}}
Replace all recursive tail calls f(x=x1, y=y1, ...) with [x, y, ...] = [x1, y1, ...]; continue;
functionsum(todo,i,total){while(true){// base case - nothing left to doif(i>=todo.length)returntotal;// recursive caseconstcurrent=todo[i];if(Array.isArray(current)){// works on a leaf node; a list of employeestotal=sumEmployeeSalaries(current,total);}else{// edge node leads to sub departmentstodo.push(...Object.values(current));}i+=1;continue;break;}}
Tidy up
functionsum(todo,i,total){while(i<todo.length){constcurrent=todo[i];if(Array.isArray(current)){// works on a leaf node; a list of employeestotal=sumEmployeeSalaries(current,total);}else{// edge node leads to sub departmentstodo.push(...Object.values(current));}i+=1;}returntotal;}
Make more idiomatic
functionsumSalaries(department){lettotal=0;for(lettodo=[department],i=0;i<todo.length;i+=1){constcurrent=todo[i];if(!Array.isArray(current)){// edge node leads to sub departmentstodo.push(...Object.values(current));continue;}// works on a leaf node; a list of employeestotal=sumEmployeeSalaries(current,total);}returntotal;}
But why bother with recursion in JavaScript, especially when you can blow the stack?
Because it allows you to arrive at simple recursive solutions quickly which can then be transformed to clean iteration with a recipe.
Example: JSFiddle
Start with a body recursive solution
Convert to a tail recursive solution
Introduce a one-shot loop around the function body
Replace all recursive tail calls
f(x=x1, y=y1, ...)
with[x, y, ...] = [x1, y1, ...]; continue;
Tidy up
Make more idiomatic
[ref]
Thank you for this, seeing a more in depth example in a more practical context helps a lot!