【问题标题】:How to minimize Math calls? (Javascript)如何最小化数学调用? (Javascript)
【发布时间】:2023-03-31 19:37:01
【问题描述】:

我正在做一个Codewars challenge,其中我必须创建一个函数,其中参数 a、b 和 c 对应于二次方程 ax^2 + bx + c = 0 并求解 x。目标不仅是求解 x,而且是尽量减少浪费的 Math.sqrt 调用次数。 (您还必须返回一个具有唯一解的数组)。

我想出了一个解决方案:

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;
  }
}

我收到错误消息:

您使用 6 个 Math.sqrt 调用通过了测试。 您应该能够通过 4 次或更少的 Math.sqrt 调用通过这些测试。

我认为将表达式的平方根部分的结果存储在一个变量 (sqrt) 中会阻止它被多次调用以评估表达式并为变量赋值。但事实并非如此。

所以我有几个问题:

  • 有没有办法存储(静态)值,以便在代码中使用它时无需重新计算?
  • 这个解决方案除了调用了太多 Math.sqrt 之外,还有什么明显的遗漏吗?

【问题讨论】:

  • 您的代码正在一个调用Math.sqrt() ...
  • 一个 static 值没有得到“重新评估”,这没有任何意义。如果它需要评估,那么它首先不是静态值。
  • 您似乎正在拨打一个Math.sqrt 电话。您对变量sqrt 的后续引用不会调用该函数。此代码是否存在于可能解释其他 5 个调用的更大上下文中?
  • 您是否在测试用例中调用此函数六次?
  • 测试是否多次调用该函数?听起来好像是在要求你解 6 个方程,其中一些方程的系数相同。

标签: javascript math


【解决方案1】:

添加c 为零时的情况:

....
var sqrt = c==0?Math.abs(b):Math.sqrt(b*b - 4*a*c);
....

[编辑]

此外,为了通过所有测试,您的解决方案在此处划分时需要括号:

xVals.push((-b - sqrt)/(2*a));
xVals.push((-b + sqrt)/(2*a));

【讨论】:

  • 具有此修改的 OPs 代码未通过测试。只需转到网站 OP 链接 - 您只需粘贴代码并单击“提交”即可,非常快。使用这一行修改粘贴 OP 代码时失败。
  • 我做了,但由于其他原因它没有通过。通过我的更改,“6 Math.sqrt”调用问题得到解决。实际上它说:You passed the tests using 4 Math.sqrt calls. 但它因其他原因而失败。
  • 很公平,确实如此。
  • 哦,别管我之前说的你需要取b的绝对值。可能发生的最坏情况是两种解决方案将交换位置;结果是一样的。
  • @Joanvo:通过这两个修改,它成功了!谢谢你。 :)
【解决方案2】:

一个简单的方法是使用 memoization 。使用闭包来保存所用值的静态列表,这样您就不会为已经计算的值调用Math.sqrt

var cachingSqrt = (function() {
    var inputs = {};
    return function(val) {
        if (inputs.hasOwnProperty(val)) {
            return inputs[val];
        } else {
            return inputs[val] = Math.sqrt(val);
        }
    }
})();

这个过程的概括是

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);

你可以像这样使用它

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;
    }
}

【讨论】:

  • 这是一种缓存形式。您如何将其整合到 OP 的方程式中以解决他们的问题?
  • @jfriend00 只需使用sqrt 而不是Math.sqrt()?
  • 警告:这与他的局部变量sqrt冲突。
  • 这假定abc 的值在调用之间重复,因此缓存将提供好处。 OP的问题不清楚是否是这种情况。
  • @jfriend 由于 OP 在显示的代码中只调用一次,我认为它必须使用相同的值调用。
【解决方案3】:

关键是避免Math.sqrt(x)x === 0x === b^2,因为答案是已知的。这两种情况发生在b^2 === 4ac4ac === 0时,因此代码需要将这两种情况短路以避免额外的Math.sqrt()调用。

所以,所有的特殊情况都是:

  • b^2 - 4ac < 0a === 0 && b === 0 做出答案undefined
  • a === 0 时(在这种情况下,方程是线性的,而不是二次的)所以答案是-c / b
  • c === 0 产生4ac === 0 所以它只是-b / a0
  • b^2 - 4ac === 0 在这种情况下答案只是-b / (2 * a)

结合使用 Ruud 的建议和 Joanvo 的建议的固定版本,只需 4 次 Math.sqrt() 调用即可通过:

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)];
    }
}

这是一个基于上述版本的版本,并从 Juan 的答案中添加了缓存。在最初的标准测试中,这只报告了一个Math.sqrt() 操作。

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;
        }
    }
})();

【讨论】:

  • 不错的文章。 pop 已经变得多余,可以去掉了;您知道两个根不相等,因为该分支中的 delta 永远不会为零。
  • 很好的解释。谢谢你,@jfriend00
【解决方案4】:

您应该为discriminant 为零的情况添加一个快捷方式。

...
else if (b*b == 4*a*c) return [-b / (2*a)];
...

【讨论】:

  • 具有此修改的 OPs 代码未通过测试。只需转到网站 OP 链接 - 您只需粘贴代码并单击“提交”即可,非常快。使用此单行修改粘贴 OP 代码时失败。当然,OP 只问了 sqrt 的问题……
  • @Mörre:你说得对,我忘记了结果周围的括号。我修正了我的答案。
  • 这只是部分答案。它消除了一个Math.sqrt() 呼叫,但还有一个要消除。
  • @jfriend00:标准测试有点少; 1*x^2 + 0*x + 0 = 0唯一 具有零判别式的测试用例,但因为那里的 C = 0,所以 joanvo 的解决方案涵盖了相同的测试。在随机测试中,我的解决方案可能会节省一些调用,但这不计入通过分配。 Joanvo 的解决方案值得点赞。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-11-01
  • 1970-01-01
  • 2010-09-06
  • 2021-08-13
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多