# Add the number of descendants for each li node

A web page has a nested list. The goal is to insert the number of descendants for each li node (except for leaf nodes).

Here is an example of the expected output:

Animals [9]
Mammals [4]
Cows
Donkeys
Dogs
Tigers
Other [3]
Snakes
Birds
Lizards
Fishes [5]
Aquarium [2]
Guppy
Angelfish
Sea [1]
Sea trout


Here is the javascript I wrote:

function isLeaf(elem) {
if(elem.firstElementChild === null) {
return true;
}

else {
return false;
}
}

function countDescendants(elem) {
if(isLeaf(elem)) return 0;

else {
let children = elem.firstElementChild.children;
let count = 0;

for(let child of children) {
if(isLeaf(child)) {
count++;
}

else {
count = count + 1 + countDescendants(child);
}

}
return count;
}
}

function insertCount(elem) {
let count = countDescendants(elem);
let text = elem.firstChild.textContent.trim();
text = text + " [" + count + "]";
elem.firstChild.textContent = text;
}

function traverseDOM(root) {
let children = root.children;

for(let child of children) {
if(!isLeaf(child)) {
if(child.tagName === "LI") {
insertCount(child);
}
traverseDOM(child);
}
}
}

//traverse DOM tree
//try catch is for debugging only
try {
if(document.body.firstElementChild !== null) {
traverseDOM(document.body.firstElementChild);
}
}  catch (err) {
console.log(err);
}


The code works. I get this nagging feeling it could be done better. For example, is there a way to improve the countDescendants function?

I think functional coding style is best suited here, because it will significantly reduce amount of code. Also, you will have clearly separated structure consisting of functions with single responsibility.

## Suggested solution

function insertCounts(root) {
forEachElement(root, (el) => el.innerHTML = countText(count(el)) + el.innerHTML, el => !isLeaf(el) && (el.tagName === 'LI'));
}

function countText(count) {
return [\${count}];
}

function forEachElement(root, apply, predicate = () => true) {
Array.from(root.children).forEach(el => { predicate(el) && apply(el); forEachElement(el, apply, predicate) });
}

function count(root) {
return Array.from(root.children).reduce((acc, el) => isLeaf(el) ? acc + 1 : acc + count(el), 0);
}

function isLeaf(elem) {
return (elem.firstElementChild === null);
}

try {
if(document.body.firstElementChild !== null) {
insertCounts(document.body.firstElementChild);
}
}  catch (err) {
console.log(err);
}

• Just wow. This solution shows the difference between a pro and a novice. Using Array.from is clever. I absolutely agree that each function should only do one task. Couple of questions though. In the forEachElement function why have predicate = () => true? I mean if I change it to predicate I get the same output. Jun 17 '18 at 1:32
• Also I want to display the count after the text and not before. I changed the insertCount function to this: function insertCounts(root) { forEachElement(root, el => el.firstChild.textContent = el.firstChild.textContent.trim() + countText(count(el)), el => !isLeaf(el) && (el.tagName === 'LI')); } It works but is there a better way? Jun 17 '18 at 1:32
• predicate = () => true: this is just default value for a parameter. I just thinking that usual call of this function will be with two arguments, that's why I made default third one. In this particular case we have only one call of function forEachElement() and we pass third argument, so default value is redundant. Jun 18 '18 at 9:09
• Unfortunately, I don't see better way to change text content than yours. Jun 18 '18 at 9:29