javascriptrecursionpath-finding

Recursion Inconsistency in Javascript


I have written some short recursive code to try to find the minimal path sum through a 2D array in Javascript, only moving right or down from top-left to bottom-right. The code seems good to me, but basically it seems to get confused about the output of stacked function calls...

const a = [
  [ 3, 8, 8 ],
  [ 1, 4, 7 ],
  [ 8, 7, 1 ]
]
const w = a[0].length-1
const h = a.length-1
f = (s,x,y) => {
  s += a[y][x]
  console.error([s, x, y, x<w, y<h])
  if(x == w && y == h) return s
  r = x < w ? f(s,x+1,y) : 9999
  d = y < h ? f(s,x,y+1) : 9999
  console.error([s, x, y, r, d, r < d ? r : d])
  return r < d ? r : d
}
console.log(f(0,0,0))

For this array, it outputs 11 instead of 16. Attempt this online!

Looking closer the problem occurs at (1,1) where it tests the paths 4+7+1 (right, down) and 4+7+1 (down, right). Changing the last line to console.log(f(4,1,1)) makes this easier to see. You get a return value of 9 and the below error output:

[ 8, 1, 1, true, true ]
[ 7, 2, 1, false, true ]
[ 1, 2, 2, false, false ]
[ 7, 2, 1, 9999, 1, 8 ]
[ 7, 1, 2, true, false ]
[ 1, 2, 2, false, false ]
[ 7, 1, 2, 1, 9999, 8 ]
[ 8, 1, 1, 1, 8, 9 ]

Both the (2, 1) and the (1, 2) paths correctly evaluate their path sums to 8 (7+1). But then when we fall back to the (1, 1) node in the tree, it seems to think the right path evaluated to 1 instead of 8, so it consistently underestimates the path length by 7. I can't seem to understand why this is happening. I'm new to JavaScript. Can someone please explain what's going wrong here?


Solution

  • The reason you get 11 instead of 16 is that you have not defined r as a local variable, but (implicitly) as a global variable. This means that every execution of f that assigns a value to r, does so to the same variable which is shared by all these executions.

    By consequence, the recursive call that is made after the assignment to r may still change the value of r and so overwrite what was assigned in that execution context that made the recursive call.

    If you prefix the assignment to r with const , then the output will be the expected 16.

    To avoid surprises like this, it is a good habit to always:

    Here is your code with those guidelines applied, without making any other change:

    "use strict";
    
    const matrix = [
        [ 3, 8, 8 ],
        [ 1, 4, 7 ],
        [ 8, 7, 1 ]
    ];
    
    const maxX = matrix[0].length - 1;
    const maxY = matrix.length - 1;
    const getMinPathCost = (cost, x, y) => {
        cost += matrix[y][x];
        if (x == maxX && y == maxY) return cost;
        const right = x < maxX ? getMinPathCost(0, x + 1, y) : 9999;
        const down = y < maxY ? getMinPathCost(0, x, y + 1) : 9999;
        return right < down ? cost + right : cost + down;
    }
    console.log(getMinPathCost(0, 0, 0));

    There is more that could be improved to this code, like:

    ...but that is beyond the scope of your question.