Home > Software engineering >  Nested object recursion trouble
Nested object recursion trouble

Time:06-25

I am trying to write a recursive function that will check each nested object for an even number and return a final sum of those numbers. I am struggling to debug the function I have so far.

function nestedEvenSum(obj, sum = 0) {
  for(const k in obj) {
    if (obj[k].constructor === Object) {
      return sum  = nestedEvenSum(obj[k], sum);
    }
    if (typeof obj[k] === "number" && obj[k] % 2 === 0) {
      sum  = obj[k];
    }
    }
  return sum;
}

const obj = {
  a: 2,
  c: {c: {c: 2}, cc: 'b', ccc: 5},
  e: {e: {e: 2}, ee: 'car'}
}

console.log(nestedEvenSum(obj));

The function returns 8. It should return 6.

Also, I noticed that it forgets about object e completely, the last object called recursively is {c: 2}.

CodePudding user response:

This exhibits a classic recursion antipattern: passing the result (sum) down the call stack as a parameter while also trying to pass it up as a result, leading to a confused state of affairs and double-counting.

Here's a fundamental rule of thumb for recursion: data dependencies (the things used to compute a result) are the parameters, results are return values.

Make sum local to the frame, then accumulate on it during the frame, either because each element is a number (leaf node in the tree search) or it's a child that should be explored recursively. Don't return immediately in the loop or you will miss some of the children.

function nestedEvenSum(obj) {
  let sum = 0;

  for (const k in obj) {
    if (obj[k].constructor === Object) {
      sum  = nestedEvenSum(obj[k]);
    }
    else if (typeof obj[k] === "number" && obj[k] % 2 === 0) {
      sum  = obj[k];
    }
  }

  return sum;
}

const obj = {
  a: 2,
  c: {
    c: {
      c: 2
    },
    cc: 'b',
    ccc: 5
  },
  e: {
    e: {
      e: 2
    },
    ee: 'car'
  }
};

console.log(nestedEvenSum(obj));

Note that this algorithm ignores arrays.

Also note that the function's design is highly rigid due to the % 2 === 0 predicate. You might consider using a function that traverses any nested structure and returns an array or generator of results that can then be filtered, or a function that allows an arbitrary callback predicate to perform the filtering.

One exception to the one-way data flow rule is that sometimes you'll want to accumulate results onto a parameter array as an optimization rather than returning and merging multiple arrays as you move back up the call stack, but that doesn't apply here.

CodePudding user response:

I think I figured it out.

First, you're returning if you find an object, which means you'll stop early, so I removed the early 'return'.

Second, you're double-counting if you find an object, because you're passing in the sum you already have and then adding it to the sum you already have.

Check this out, just a couple small changes:

function nestedEvenSum(obj, sum = 0) {
  for(const k in obj) {
    if (obj[k].constructor === Object) {
      sum = nestedEvenSum(obj[k], sum);
    }
    if (typeof obj[k] === "number" && obj[k] % 2 === 0) {
      sum  = obj[k];
    }
  }
  return sum;
}

const obj = {
  a: 2,
  c: {c: {c: 2}, cc: 'b', ccc: 5},
  e: {e: {e: 2}, ee: 'car'}
}

console.log(nestedEvenSum(obj));

CodePudding user response:

You need to remove the first return statement.

To get a shorter code, you could use conditional statements.

This approach does keep the sum of the actual object and does not hand over the sum to the nested level.

The handing over is only needed for tail call optimization (TCO) because of replacing the old function with the recursive funtion of the stack. Actuall TCO is in Javascript not widely supported ...

function nestedEvenSum(obj) {
    let sum = 0;
    for (const k in obj) {
        sum  = obj[k] && typeof obj[k] === 'object'
            ? nestedEvenSum(obj[k])
            : obj[k] % 2 === 0
                ? obj[k]
                : 0;
    }
    return sum;
}

const obj = { a: 2, c: { c: { c: 2 }, cc: 'b', ccc: 5 }, e: { e: { e: 2 }, ee: 'car' } };

console.log(nestedEvenSum(obj));

  • Related