Javascript性能:reduce() vs for-loop

时间:2022-12-04 21:19:39

I was attempting this Codewars challenge and the problem involves finding the divisors of a number and then calculating the sum of these divisors squared. I found two approaches to this problem.

我正在尝试这个Codewars的挑战,问题是找到一个数字的因数,然后计算这些因数的平方的和。我找到了解决这个问题的两种方法。

The first approach is based on another * questions about finding the sum of all divisors and seems clever at first:

第一个方法是基于另一个*的问题,关于找到所有因数的和,并且看起来很聪明:

function divisorsSquared(n) {
  // create a numeric sequence and then reduce it
  return [...Array(n+1).keys()].slice(1)
   .reduce((sum, num)=>sum+(!(n % (num)) && Math.pow(num,2)), 0);
}

The second approach I used was using a simple for-loop:

我使用的第二种方法是使用简单的for-loop:

function divisorsSquared(n) {
  var sum = 0;
  for(var i = 1; i<= n; i++){
     if(n % i === 0) sum += Math.pow(i,2); 
  }
  return sum;
}

Now I noticed that the first approach is significantly slower than the second and a quick jsperf test confirms this.

现在我注意到第一种方法比第二种方法要慢得多,而且快速的jsperf测试证实了这一点。

My questions are: Why is the first approach so much slower and what approach is preferable in production code?

我的问题是:为什么第一种方法要慢得多?在生产代码中,哪种方法更可取?

On Codewars I notice that for many challenges there are clever one-line solutions using similar array methods. As a beginner, may such solutions be considered better practice than for-loops, even if performance is worse?

在Codewars中,我注意到许多挑战都有使用类似数组方法的单行解决方案。作为初学者,这样的解决方案是否可以被认为是比for循环更好的实践,即使性能更差?

3 个解决方案

#1


2  

Functional or imperative approaches makes a difference in JS. Imperative always wins. Yet, the real thing is most of time a better algorithm is the winner. Your code is a naive approach. You can tune it to work much better just by checking the integers up until the square root of the target value and you will get two answers per check. If target is 100 if 2 is a dividend then 100/2 must be a dividend too.. So it's fair to check up to Math.sqrt(100) - 1 and handle 10 with care in order to not consider it twice.

函数或命令式方法对JS有影响。必须总是赢家。然而,真正的问题是大多数时候,更好的算法才是赢家。您的代码是一种幼稚的方法。你可以通过检查整数直到目标值的平方根,然后得到两个答案。如果目标是100如果2是股息那么100/2也一定是股息。因此,检查Math.sqrt(100) - 1并小心地处理10,以便不考虑它两次是公平的。

Accordingly now the functional solution with reduce beats the imperative naive solution.

因此,现在的功能解决方案和减法的要求是很幼稚的解决方案。

function divisorsSquared(n) {
  var sn = Math.sqrt(n);
  return Array.from({length:~~sn-1},(_,i) => i+1)
              .reduce((s,m) => n%m ? s : s + m*m + (n/m)*(n/m), 0) + (n%sn ? 0 : sn*sn);
}
var result = 0;
console.time("functional and tuned");
result = divisorsSquared(1000000);
console.timeEnd("functional and tuned");
console.log("for input: 1000000 the result is:",result);


function dvssqr(n) {
  var sum = 0;
  for(var i = 1; i<= n; i++){
     if(n % i === 0) sum += Math.pow(i,2); 
  }
  return sum;
}

console.time("imperative and naive");
result = dvssqr(1000000);
console.timeEnd("imperative and naive");
console.log("for input: 1000000 the result is:",result);

#2


4  

  • Array(n+1) allocates an array with n + 1 elements, Array(n+1).keys() returns an iterator over the created array's indices, but the spread operator [...Iterator] helps "unwrap" this iterator into yet another array, then finally slice(1) comes along to copy the secondly created array starting at index 1 which allocates yet another array (third one) with the number 0 discarded. So that were 3 allocations but 2 were dicarded. Your for-loop does not allocate any arrays, it is a simple traversal in O(n) with only 2 allocations for i and sum, so it is more efficient
  • 数组(n+1)分配一个包含n+1元素的数组,数组(n+1).keys()返回一个迭代器,遍历创建的数组的索引,但是扩展操作符[…迭代器]帮助将这个迭代器“展开”到另一个数组中,最后切片(1)复制第二个创建的数组,该数组从索引1开始,它分配了另一个数组(第三个),并将数字0丢弃。这是3个分配但2个被分配。for-loop不会分配任何数组,它只是O(n)中的一个简单遍历,只有2个分配给i和sum,所以它更高效
  • sum+(!(n % (num)) && Math.pow(num,2)) is essentially the same as if(n % i === 0) sum += Math.pow(i,2); but the if approach is way more readable. If I were the judge, I would pick the second approach because it is more memory efficient, yet it favors readability.
  • 总和+(!(n % (num)) && Math.pow(num,2)本质上与(n % i == 0)和+= Math.pow(i,2)相同;但是if方法更具有可读性。如果我是法官,我会选择第二种方法,因为它更节省内存,但更有利于可读性。

#3


2  

Looking into the code, for loop is obviously less complex and more readable.

查看代码,for循环显然不那么复杂,可读性也更好。

Consider you are working within a team, maximum number of your team members will know what the code is doing right away. Some will have to look up what the reduce() method is, but then they'll also know what's going on. So here, a for loop is easier for others to read and understand.

考虑到您正在一个团队中工作,您的团队成员的最大数量将立即知道代码在做什么。有些人必须查找reduce()方法是什么,但是他们也会知道发生了什么。因此,对于其他人来说,for循环更容易阅读和理解。

On the other side, native array functions (filter(), map(), reduce()) will save you from writing some extra code and also slower in performance.

另一方面,本机数组函数(filter()、map()、reduce())将节省您编写一些额外的代码,并且在性能上也会更慢。

For a beginner, I think for-loops should be better over native array functions.

对于初学者,我认为For循环应该比本地数组函数更好。

#1


2  

Functional or imperative approaches makes a difference in JS. Imperative always wins. Yet, the real thing is most of time a better algorithm is the winner. Your code is a naive approach. You can tune it to work much better just by checking the integers up until the square root of the target value and you will get two answers per check. If target is 100 if 2 is a dividend then 100/2 must be a dividend too.. So it's fair to check up to Math.sqrt(100) - 1 and handle 10 with care in order to not consider it twice.

函数或命令式方法对JS有影响。必须总是赢家。然而,真正的问题是大多数时候,更好的算法才是赢家。您的代码是一种幼稚的方法。你可以通过检查整数直到目标值的平方根,然后得到两个答案。如果目标是100如果2是股息那么100/2也一定是股息。因此,检查Math.sqrt(100) - 1并小心地处理10,以便不考虑它两次是公平的。

Accordingly now the functional solution with reduce beats the imperative naive solution.

因此,现在的功能解决方案和减法的要求是很幼稚的解决方案。

function divisorsSquared(n) {
  var sn = Math.sqrt(n);
  return Array.from({length:~~sn-1},(_,i) => i+1)
              .reduce((s,m) => n%m ? s : s + m*m + (n/m)*(n/m), 0) + (n%sn ? 0 : sn*sn);
}
var result = 0;
console.time("functional and tuned");
result = divisorsSquared(1000000);
console.timeEnd("functional and tuned");
console.log("for input: 1000000 the result is:",result);


function dvssqr(n) {
  var sum = 0;
  for(var i = 1; i<= n; i++){
     if(n % i === 0) sum += Math.pow(i,2); 
  }
  return sum;
}

console.time("imperative and naive");
result = dvssqr(1000000);
console.timeEnd("imperative and naive");
console.log("for input: 1000000 the result is:",result);

#2


4  

  • Array(n+1) allocates an array with n + 1 elements, Array(n+1).keys() returns an iterator over the created array's indices, but the spread operator [...Iterator] helps "unwrap" this iterator into yet another array, then finally slice(1) comes along to copy the secondly created array starting at index 1 which allocates yet another array (third one) with the number 0 discarded. So that were 3 allocations but 2 were dicarded. Your for-loop does not allocate any arrays, it is a simple traversal in O(n) with only 2 allocations for i and sum, so it is more efficient
  • 数组(n+1)分配一个包含n+1元素的数组,数组(n+1).keys()返回一个迭代器,遍历创建的数组的索引,但是扩展操作符[…迭代器]帮助将这个迭代器“展开”到另一个数组中,最后切片(1)复制第二个创建的数组,该数组从索引1开始,它分配了另一个数组(第三个),并将数字0丢弃。这是3个分配但2个被分配。for-loop不会分配任何数组,它只是O(n)中的一个简单遍历,只有2个分配给i和sum,所以它更高效
  • sum+(!(n % (num)) && Math.pow(num,2)) is essentially the same as if(n % i === 0) sum += Math.pow(i,2); but the if approach is way more readable. If I were the judge, I would pick the second approach because it is more memory efficient, yet it favors readability.
  • 总和+(!(n % (num)) && Math.pow(num,2)本质上与(n % i == 0)和+= Math.pow(i,2)相同;但是if方法更具有可读性。如果我是法官,我会选择第二种方法,因为它更节省内存,但更有利于可读性。

#3


2  

Looking into the code, for loop is obviously less complex and more readable.

查看代码,for循环显然不那么复杂,可读性也更好。

Consider you are working within a team, maximum number of your team members will know what the code is doing right away. Some will have to look up what the reduce() method is, but then they'll also know what's going on. So here, a for loop is easier for others to read and understand.

考虑到您正在一个团队中工作,您的团队成员的最大数量将立即知道代码在做什么。有些人必须查找reduce()方法是什么,但是他们也会知道发生了什么。因此,对于其他人来说,for循环更容易阅读和理解。

On the other side, native array functions (filter(), map(), reduce()) will save you from writing some extra code and also slower in performance.

另一方面,本机数组函数(filter()、map()、reduce())将节省您编写一些额外的代码,并且在性能上也会更慢。

For a beginner, I think for-loops should be better over native array functions.

对于初学者,我认为For循环应该比本地数组函数更好。