ループの最後の要素は別の扱いに値しますか?
-
03-07-2019 - |
質問
レビュー中に、次のようなループが発生することがあります:
i = begin
while ( i != end ) {
// ... do stuff
if ( i == end-1 (the one-but-last element) ) {
... do other stuff
}
increment i
}
次に質問をします:これを書いていただけますか?
i = begin
mid = ( end - begin ) / 2 // (the middle element)
while ( i != end ) {
// ... do stuff
if ( i > mid ) {
... do other stuff
}
increment i
}
私の意見では、これはループを書くという意図に反します。各要素に共通することがあるため、ループします。このコンストラクトを使用して、いくつかの要素について何か違うことをします。そのため、これらの要素には個別のループが必要であると結論付けています。
i = begin
mid = ( end - begin ) / 2 //(the middle element)
while ( i != mid ) {
// ... do stuff
increment i
}
while ( i != end ) {
// ... do stuff
// ... do other stuff
increment i
}
今、SOで質問を見ました。 if
-clauseを良い方法で書いてください...そして悲しくなりました:何かがここにありません。
間違っていますか?もしそうなら、特別なケースでループ本体を乱雑にすることで何が良いのでしょうか?
解決
@xtofl、
あなたの懸念に同意します。
同様の問題が何百万回も発生しました。
どちらの開発者も、最初または最後の要素に特別な処理を追加します。
ほとんどの場合、 startIdx + 1 または endIdx-1 要素からループするか、1つの長いループを複数の短いループに分割するだけの価値があります。
非常にまれなケースでは、ループを分割することはできません。
私の意見では、珍しい物事は可能な限りループ外で処理すべきです。
他のヒント
この質問は原則によって答えられるべきではないと思います(たとえば、ループ内で<!> quot;すべての要素を平等に扱う<!> quot;)。代わりに、実装が良いか悪いかを評価する2つの要因を見ることができます:
- ランタイムの有効性-コンパイルされたコードは高速で実行されますか、それとも異なる方法でより高速になりますか?
- コードの保守性-(別の開発者にとって)ここで何が起こっているのかを理解するのは簡単ですか?
すべてを1つのループで実行することで、より高速でコードが読みやすくなる場合は、そのようにします。遅くて読みにくい場合は、別の方法で行ってください。
高速で読みにくい、または遅いが読みやすい場合は、特定のケースでどの要素がより重要かを調べ、ループする方法(またはループしない方法)を決定します。
人々が配列の要素をコンマ区切りの文字列に結合しようとしたとき、私はこれを見たことがあることを知っています:
for(i=0;i<elements.size;i++) {
if (i>0) {
string += ','
}
string += elements[i]
}
その中にif句があるか、最後に文字列+ =行を再度複製する必要があります。
この場合の明らかな解決策は
string = elements.join(',')
ただし、joinメソッドは内部的に同じループを実行します。そして、あなたが望むことをする方法が常にあるとは限りません。
forループに特殊なケースを入れると、通常は自分の利益のためにあまりにも賢いことに気付きました。
最後に投稿したスニペットでは、// .... do stuffのコードを繰り返しています。
異なるインデックスセットに対してまったく異なる操作セットがある場合、2つのループを保持する意味があります。
i = begin
mid = ( end - begin ) / 2 //(the middle element)
while ( i != mid ) {
// ... do stuff
increment i
}
while ( i != end ) {
// ... do other stuff
increment i
}
これは事実ではありませんが、1つのループを維持したいでしょう。ただし、実際に保存するのは(end-begin)/ 2回の比較です。つまり、コードをきれいに見せたいか、CPUサイクルをいくらか節約したいかに要約されます。電話はあなた次第です。
完全に釘付けされていると思います。ほとんどの人は、ループ内に条件分岐を含めるというトラップに陥ります。外部で実行できる場合は、単純に高速です。
例:
if(items == null)
return null;
StringBuilder result = new StringBuilder();
if(items.Length != 0)
{
result.Append(items[0]); // Special case outside loop.
for(int i = 1; i < items.Length; i++) // Note: we start at element one.
{
result.Append(";");
result.Append(items[i]);
}
}
return result.ToString();
そしてあなたが説明した真ん中のケースは、単純な厄介なです。そのコードが大きくなり、さまざまなメソッドにリファクタリングする必要がある場合を想像してください。
XMLを解析していない限り<!> lt; grin <!> gt;ループはできるだけシンプルで簡潔に保つ必要があります。
すべての要素を平等に扱うことを意図したループについて、あなたは正しいと思います。残念ながら、時には特別なケースがありますが、これらはifステートメントを介してループ構造内で処理する必要があります。
特別なケースがたくさんある場合でも、別々のコンストラクトで2つの異なる要素セットを処理する方法を考えるべきでしょう。
単純に、ループから要素を除外したい ループ外で別の処理を行います
例:EOFの場合を考えてみましょう
i = begin
while ( i != end -1 ) {
// ... do stuff for element from begn to second last element
increment i
}
if(given_array(end -1) != ''){
// do stuff for the EOF element in the array
}
もちろん、引き出し可能なループ内の特別なケースはばかげています。ただし、do_stuffも複製しません。コードをコピーアンドペーストしないように、関数またはマクロに配置します。
もう1つ見たくないのは、 for-caseパターン:
for (i=0; i<5; i++)
{
switch(i)
{
case 0:
// something
break;
case 1:
// something else
break;
// etc...
}
}
これは実際のコードで見ました。
パフォーマンスが良いのはどれですか
アイテムの数が非常に多い場合、特にすべてのアイテムで some 操作を実行する場合は特に、1回ループします。条件を評価するコストは、2回ループするよりも少ない可能性があります。
もちろん、2回ループしているわけではありません...この場合、2つのループが望ましいです。ただし、パフォーマンスを第一に考慮する必要があると考えています。ループの境界を1回操作するだけで作業を分割できる場合(N回)、ループに条件を設定する必要はありません。
特別な場合は、1回だけ実行する場合はループ外で行う必要があります。
ただし、スコーピングによりループ内に保持しやすいインデックスまたは他の変数が存在する場合があります。ループ制御構造内でデータ構造上のすべての操作をまとめて保持する文脈上の理由もあるかもしれませんが、それ自体は弱い議論だと思います。
必要と利便性に応じて使用するだけです。したがって、要素を平等に扱うという言及はなく、言語が提供する機能を損なうことは確かにありません。