2017-04-22 10 views
10

私はthis Codewars challengeを試していましたが、問題は数値の除数を見つけて、これらの除数の平方和を計算することです。この問題には2つのアプローチがありました。JavaScriptパフォーマンス:reduce()とforループの比較

最初のアプローチは、finding the sum of all divisorsについては、別のStackOverflowの質問に基づいており、最初に巧妙なようです:

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

私は、単純なforループを使用していた使用される第二のアプローチ:今

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

をI最初のアプローチが2番目のアプローチよりも大幅に遅く、迅速なjsperf testがこれを確認することに気づいた。

私の質問は次のとおりです。なぜ最初のアプローチが非常に遅く、プロダクションコードではどのようなアプローチが望ましいのですか?

コードワーズでは、多くの課題に対して、同様の配列メソッドを使用する賢明な1行のソリューションがあることに気付きました。初心者としては、たとえパフォーマンスが悪化したとしても、そのようなソリューションをfor-loopsよりも優れた方法と考えることができますか?

+5

あなたはコードを見れば、削減する反復処理し、その後、最初のものは、コンストラクタを使用して、その後、繰り返し処理が普及し、その後、繰り返し処理は、キーを取得するには、その後、繰り返し処理はスライスします... 2回目は一度繰り返す。 – adeneo

+0

私はそれについては考えていませんでしたが、今はかなり明らかです。ありがとう! –

答えて

2

機能的または命令的アプローチがJSを変えます。命令的なものが常に勝つ。 しかし、本当のことは、より良いアルゴリズムが勝者の大部分です。あなたのコードは素朴なアプローチです。目標値の平方根まで整数をチェックするだけで、はるかにうまく動作するようにチューニングすることができ、チェックごとに2つの答えが得られます。目標が100なら2が配当ですので100/2も配当でなければなりませんのでにチェックして10を2度考慮しないように注意してください。

したがって、低減された機能的な解決策は、命令的でナイーブな解決策に打ち勝っています。

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

4
  • Array(n+1)のn + 1個の要素、Array(n+1).keys()を持つ配列が作成された配列のインデックスの反復子を返しますが、スプレッドオペレータ[...Iterator]は、さらに別の配列に、このイテレータを「アンラップ」助け、最後にslice(1)をコピーするためにやって来る割り当てインデックス1で始まる2番目に作成された配列は、番号0が破棄されたさらに別の配列(3番目)を割り当てます。したがって、3つの割り当てがあったが、2つは割り当てられなかった。あなたfor-loopは、isumに対してのみ2の割り当てと(N)Oで簡単トラバーサルである、任意の配列を割り当てないので、
  • sum+(!(n % (num)) && Math.pow(num,2))
  • if(n % i === 0) sum += Math.pow(i,2);と本質的に同じであるが、 ifアプローチは方法より読みやすく、より効率的です。私が裁判官であった場合は、より効率的なメモリですが、読みやすさの点で有利です。
2

コードを見ると、forループは明らかにあまり複雑でなく読みやすくなります。

あなたはチーム内で作業していると考えてください。チームメンバーの最大人数は、コードがすぐに何をしているのか分かります。 reduce()メソッドが何であるかを調べなければならない人もいますが、何が起こっているのかもわかります。 ここで、forループは他の人が読んで理解しやすいものです。

一方、ネイティブ配列関数(filter(), map(), reduce())は、いくつかの特別なコード の作成を防ぎ、パフォーマンスも低下します。

初心者の方は、for-loopsはネイティブ配列関数より優れているはずです。