forループ注釈でこの追加変数を使用する利点はありますか?


11

私が取り組んでいる大きなプロジェクト(擬似コード)で次のループアノテーションを見つけました。

var someOtherArray = [];
for (var i = 0, n = array.length; i < n; i++) {
    someOtherArray[i] = modifyObjetFromArray(array[i]);
}

私の注意を引いたのは、この余分な「n」変数です。このように書かれたfor lopを見たことはありません。

明らかにこのシナリオでは、このコードを次の方法で書くことができなかった理由はありません(私は非常に慣れています):

var someOtherArray = [];
for (var i = 0; i < array.length; i++) {
    someOtherArray[i] = modifyObjetFromArray(array[i]);
}

しかし、それは私に考えさせられました。

そのようなforループを書くことが理にかなっているシナリオはありますか?「配列」の長さはforループの実行中に変わる可能性があるという考えが浮かびますが、元のサイズよりもさらにループしたくはありませんが、そのようなシナリオは想像できません。

OutOfBoundsExceptionが発生する可能性が高いため、ループ内で配列を縮小してもあまり意味がありません。

このアノテーションが役立つ既知のデザインパターンはありますか?

編集 @ Jerry101で述べたように、理由はパフォーマンスです。これが、私が作成したパフォーマンステストへのリンクです:http : //jsperf.com/ninforloop。私の意見では、非常に巨大な配列を繰り返し処理しているのでなければ、違いは十分に大きくありません。コピー元のコードは最大20個の要素しか持っていなかったため、この場合の読みやすさはパフォーマンスの考慮事項を上回ると思います。


編集に対する反応:これはオブジェクト指向プログラミングです。標準のarray.lengthは高速で安価です。しかし、何らかの理由で、.lengthの実装は高価なものに変更される可能性があります。値が同じままの場合、複数回取得しないでください。
ピーターB

同意する。このオプションについて知っておくのは良いことですが、SQLクエリに似た例を取得しない限り、おそらくあまり使用しないでしょう。ありがとうございます
Vlad Spreys

つまり、配列が100万個のアイテムである場合、値が同じままである間、array.lengthを1回ではなく100万回呼び出すことになります。何百万回も質問し、その後の各回答が最初の回答と同じであることを知っている......私には少し無意味に聞こえます。
ピーターB

1
これによりコードが著しく読みにくくなるとは思いません。(真剣に、このような単純なループで問題が発生した場合は、心配する必要があります。)しかし、1)40年にわたるCおよびC下降言語の後、すべての深刻なコンパイラーがまだこれをしていなかったら、驚くでしょうあなたのために; 2)これはホットスポットではない可能性があります。ライブラリ内(データ構造の実装など)でない限り、解析に5秒余分に費やす価値はないようです
。– Doval

1
C#/。NETでは、使用するバリアントは、使用するバリアントnよりも遅い場合がarray.Lengthあります。これは、JITterが配列境界チェックを削除できることに気付かない場合があるためです。
CodesInChaos

回答:


27

この変数nは、生成されたコードがすべての反復で配列の長さをフェッチしないようにします。

これは、使用する言語、配列が実際にコレクションオブジェクトであるかJavaScript「配列」であるか、その他の最適化の詳細に応じて実行時間に違いをもたらす可能性がある最適化です。


3
いいえ、そうではありません。反復ごとに配列の長さを取得するかどうかは、言語だけでなく、コンパイラーとコンパイラーのオプション、およびループで行われることにも依存します(CとC ++について話している)
B –овић

..それでも、私が本当に望むなら、n代入をループのすぐ上に配置します。OPが指摘しているように、これは使用するのがまれな(YMMV)構造であり、他の開発者が読みにくいコード。
cwap

20
書かれているように、それnはループにスコープされます。これは通常、良いことです。後で再び使用する場合にのみ、ループの前に定義します。
Jerry101

4
@ Jerry101:どうやらスニペットの例はJavaScriptであり、ループスコープのようなものはありません…
Bergi

1
@BЈовић:実際には、コンパイラーは配列サイズが変化しないことを証明することはめったにありません(通常、ループしているベクトルが関数に対してローカルであり、ループ内のインライン関数のみを呼び出している場合にのみ、簡単に証明可能です)。
マッテオイタリア

2

配列の長さを取得することは、実際に反復するアクションよりも簡単に「より高価」になります。

したがって、セットが変更されない場合は、長さを一度だけ照会します。

配列ではなく、SQLサーバーからのレコードセットを使用して、繰り返しごとにレコードカウントをクエリしないことで劇的な改善が見られました。(もちろん、これを行うのは、このプロセス中に配列またはレコードセットが変更されないことを保証できる場合のみです)。


レコード数が変更された場合、何をしますか?100個のアイテムがあり、アイテム50を処理している間に、#25以降のアイテムが挿入されたとします。したがって、アイテム#51を処理すると、すでに処理した#50と同じになり、問題が発生します。したがって、問題は長さが変化することではなく、はるかに広範囲に及ぶことです。
gnasher729

@ gnasher729非対称的な動作に陥ると、多くの問題が発生する可能性があります。ただし、開始やSQLトランザクションなど、それに対してできることはあります。
ピーターB

2

「配列」の長さはforループの実行中に変わる可能性があるという考えが浮かびますが、元のサイズよりもさらにループしたくはありませんが、そのようなシナリオは想像できません。

パフォーマンスについて話している人々はおそらくこれがそのように書かれている理由で正しいでしょう(そのように書いた人はパフォーマンスに大きな影響を与えることは正しくないかもしれませんが、それは別の問題です)。

ただし、あなたの質問のこの部分に答えるために、ループの主な目的がループしている配列を変更することである場合に起こる可能性が最も高いと思います。擬似コードで、次のようなものを検討してください。

guests = [];
for (i = 0; i < invitations.length; ++i) {
    if (! invitations[i].is_declined()) {
        guests.append(invitations[i].recipient())
    }
}
// maybe some other stuff to modify the guestlist
for (i = 0, n = guests.length; i < n; ++i) {
    if (guests[i].has_plus_one()) {
        guests.append(guests[i].get_plus_one());
    }
}

もちろん、それを書く他の方法があります。この場合、受信者が招待を受け入れたかどうかを確認すると同時に、プラスのものを確認し、一度に1つずつ招待状を作成します。これらの2つの操作を必ずしも組み合わせたいわけではありませんが、それが間違いであり、この設計が必要である理由をすぐに考えることはできません。場合によっては検討するオプションです。

実際、このコードで最も間違っているのは、guests配列のメンバーシップがコードの異なるポイントで異なることを意味することだと思います。したがって、私は確かにそれを「パターン」とは呼びませんが、この種のことをやめることを除外するのに十分な欠陥であることは知りません:-)


配列は別のスレッドによって変更される可能性もあります。これにより、コンパイラは長さの取得を最適化できなくなります。そのため、問題のプログラマーは、他のスレッドでも配列は変わらないと明示的に述べています。
バシレフ

0

可能な限り、配列のすべての要素を走査する反復子を使用する必要があります。配列をランダムアクセスストレージとしてではなく、シーケンスとして使用するため、シーケンシャルアクセスを行うイテレータの方が高速であることは明らかです。配列は実際には二分木であると思いますが、誰かが要素を数えることで「長さ」を決定するコードと、i番目の要素にアクセスするコードを書きました。 )。イテレータは非常に高速であり、ループはスローになります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.