Reputation: 3231
I'm doing a Codewars challenge in which I have to create a function with the parameters a, b & c corresponding to the quadratic equation ax^2 + bx + c = 0 and solve for x. The goal is not only to solve for x, but to minimize the number of spendy Math.sqrt calls. (You also have to return an array with the unique solution(s)).
I came up with a solution:
function solveQuadratic(a, b, c) {
if ((4*a*c > b*b) || ((a === 0) && (b === 0))) { return undefined;}
else if (a === 0) {return [-c/b];}
else {
var xVals = [];
var sqrt = Math.sqrt(b*b - 4*a*c);
xVals.push((-b - sqrt)/2*a);
xVals.push((-b + sqrt)/2*a);
if (xVals[0] === xVals[1]) {xVals.pop();}
return xVals;
}
}
I got the error message:
You passed the tests using 6 Math.sqrt calls. You should be able to pass these tests with 4 Math.sqrt calls or less.
I thought storing the result of the square root part of the expression in a variable (sqrt) would prevent it from being called more than that one time to evaluate the expression and assign a value to the variable. But that's not the case.
So I have a couple of questions:
Upvotes: 1
Views: 223
Reputation: 707396
The key is to avoid Math.sqrt(x)
when x === 0
and when x === b^2
since the answer is already known. These two situations occur when b^2 === 4ac
and when 4ac === 0
, so the code needs to short circuit those two cases to avoid the extra Math.sqrt()
calls.
So, all the special cases are:
b^2 - 4ac < 0
or a === 0 && b === 0
which make the answer undefined
.a === 0
(in which case the equation is linear, not quadratic) so the answer is -c / b
.c === 0
which makes 4ac === 0
so it's just -b / a
and 0
.b^2 - 4ac === 0
in which case the answer is just -b / (2 * a)
Using a combination of Ruud's suggestion and a fixed version of Joanvo's suggestion, it will pass with only 4 Math.sqrt()
calls with this:
function solveQuadratic(a, b, c) {
var delta = (b * b) - (4 * a * c), sqrt;
if ((delta < 0) || ((a === 0) && (b === 0))) {
return undefined;
} else if (a === 0) {
return [-c / b];
} else if (c === 0) {
return b === 0 ? [0] : [-b / a, 0];
} else if (delta == 0) {
return [-b / (2 * a)];
} else {
sqrt = Math.sqrt(delta);
return [(-b - sqrt) / (2 * a), (-b + sqrt) / (2 * a)];
}
}
Here's a version that builds on the above version and adds the cache from Juan's answer. In the initial standard test, this reports only one Math.sqrt()
operation.
function solveQuadratic(a, b, c) {
var delta = (b * b) - (4 * a * c), sqrt;
if ((delta < 0) || ((a === 0) && (b === 0))) {
return undefined;
} else if (a === 0) {
return [-c / b];
} else if (c === 0) {
return b === 0 ? [0] : [-b / a, 0];
} else if (delta == 0) {
return [-b / (2 * a)];
} else {
sqrt = sqrt2(delta);
return [(-b - sqrt) / (2 * a), (-b + sqrt) / (2 * a)];
}
}
var sqrt2 = (function() {
var cache = {0:0, 1:1, 4:2, 9:3};
return function(x) {
if (cache.hasOwnProperty(x)) {
return cache[x];
} else {
var result = Math.sqrt(x);
cache[x] = result;
return result;
}
}
})();
Upvotes: 2
Reputation: 11018
You should add a shortcut for cases where the discriminant is zero.
...
else if (b*b == 4*a*c) return [-b / (2*a)];
...
Upvotes: 1
Reputation: 5817
Add a case for when c
is zero:
....
var sqrt = c==0?Math.abs(b):Math.sqrt(b*b - 4*a*c);
....
[Edit]
Also, to pass all the tests, your solution needs parenthesis when dividing here:
xVals.push((-b - sqrt)/(2*a));
xVals.push((-b + sqrt)/(2*a));
Upvotes: 3
Reputation: 92274
An easy way is to use memoization . Use a closure to keep a static list of values used so you don't call Math.sqrt
for values you've already calculated
var cachingSqrt = (function() {
var inputs = {};
return function(val) {
if (inputs.hasOwnProperty(val)) {
return inputs[val];
} else {
return inputs[val] = Math.sqrt(val);
}
}
})();
A generalization of this process would be
function createCachedResults(fn, scope) {
var inputs = {};
return function(val) {
if (inputs.hasOwnProperty(val)) {
return inputs[val];
} else {
return inputs[val] = fn.call(scope, val);
}
}
}
cachingSqrt = createCachedResults(Math.sqrt, Math);
And you could use it like
var cachingSquareRoot = createCachedResults(Math.sqrt, Math);
function solveQuadratic(a, b, c) {
if ((4*a*c > b*b) || ((a === 0) && (b === 0))) {
return undefined;
}
else if (a === 0) {
return [-c/b];
} else {
var xVals = [];
var sqrt = cachingSquareRoot(b*b - 4*a*c);
xVals.push((-b - sqrt)/2*a);
xVals.push((-b + sqrt)/2*a);
if (xVals[0] === xVals[1]) {
xVals.pop();
}
return xVals;
}
}
Upvotes: 2