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?
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:
Declare variables with let
, const
or var
, and never let them be implicitly defined as globals. You can enforce this habit by running in strict mode.
Indent your code properly. An opening brace for a code block should not be followed by a statement on the same line. Indent lines that belong to a code block. Add spacing to make code more readable.
Separate your statements with semicolons. Don't rely on the automatic semicolon insertion system to do that job for you, but take control of that aspect.
Use variable names that are descriptive. r
and d
do not immediately indicate what they are about. right
and down
are clearer names, making the code easier to understand.
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:
Infinity
instead of 9999 for indicating the highest possible costMath.min
instead of the conditional operator to pick the least costx
and y
multiple times...but that is beyond the scope of your question.