web-dev-qa-db-ja.com

ブレーク/リターン付きのforeachループと明示的な不変条件と事後条件付きのwhileループ

これは、値が配列内にあるかどうかを確認する最も一般的な方法(私には思えます)です。

for (int x : array)
{
    if (x == value)
        return true;
}
return false;        

ただし、おそらくワースやダイクストラが何年も前に読んだ本では、このスタイルの方が良い(内部に出口があるwhileループと比較した場合)と言われています。

int i = 0;
while (i < array.length && array[i] != value)
    i++;
return i < array.length;

このようにして、追加の終了条件がループ不変の明示的な部分になり、隠された条件がなく、ループ内に終了します。すべてがより明確になり、構造化プログラミングの方法でより明確になります。私は一般に、可能な限りこの後者のパターンを優先し、forループを使用してaからbへの反復のみを行いました。

それでも、最初のバージョンが不明確であるとは言えません。多分それは、少なくとも非常に初心者にとっては、より明確で理解しやすいでしょう。それで、私はまだ自分自身に質問していますどちらが良いですか?

多分誰かが方法の1つを支持して良い理論的根拠を与えることができますか?

更新:これは、複数の関数の戻り点、ラムダ、または配列自体の要素を見つけることの問題ではありません。単一の不等式よりも複雑な不変式でループを記述する方法についてです。

更新:わかりました。答えてコメントする人のポイントがわかります:ここでforeachループを混ぜましたが、それ自体は、whileループよりもはるかに明確で読みやすくなっています。私はそれをすべきではなかった。しかし、これも興味深い質問なので、そのままにしておきます。foreachループと内部の追加条件、または明示的なループ不変式と事後条件を含むwhileループです。条件と終了/ブレークを持つforeachループが勝っているようです。 foreachループなしで追加の質問を作成します(リンクリストの場合)。

17
Danila Piatov

これらのような単純なループの場合、標準の最初の構文ははるかに明確です。複数の返品が混乱する、またはコードの匂いがすることを考える人もいますが、コードがこれほど小さい場合、これは実際の問題ではないと思います。

より複雑なループについては、もう少し議論の余地があります。ループの内容が画面に収まらず、ループ内に複数の戻りがある場合、複数の出口点によりコードの維持がより困難になる可能性があるという議論があります。たとえば、関数を終了する前にいくつかの状態維持メソッドが実行されていることを確認する必要がある場合、それをreturnステートメントの1つに追加し忘れて、バグが発生する可能性があります。すべての終了条件をwhileループでチェックできる場合、出口点は1つだけで、このコードをその後に追加できます。

とは言っても、ループでは特に、できるだけ多くのロジックを個別のメソッドに入れてみることをお勧めします。これにより、2番目の方法に利点がある多くのケースが回避されます。明確に分離されたロジックを持つ無駄のないループは、これらのスタイルのどれを使用するよりも重要です。また、アプリケーションのコードベースのほとんどが1つのスタイルを使用している場合は、そのスタイルを使用する必要があります。

19
Nathanael

これは簡単。

読者にとっては、明確さほど重要なことはほとんどありません。最初のバリエーションは、信じられないほどシンプルで明確でした。

2番目の「改善された」バージョンでは、何度か読み、すべてのEdge条件が正しいことを確認する必要がありました。

より優れたコーディングスタイルであるZERO DOUBTがあります(最初の方がはるかに優れています)。

さて、CLEARとは、人によって異なります。そのための客観的な基準があるかどうかはわかりません(このようなフォーラムに投稿して、さまざまな人々の意見を得ることは役立つでしょう)。

ただし、この特定のケースでは、最初のアルゴリズムがより明確である理由を説明できます。C++がコンテナー構文を反復処理する様子とその動作を知っています。私はそれを内部化しました。その構文を使用している誰かUNFAMILIAR(その新しい構文)は、2番目のバリエーションを好むかもしれません。

しかし、その新しい構文を知って理解したら、その基本的な概念をそのまま使用できます。ループ反復(2番目)のアプローチでは、配列全体をループするために、ユーザーがすべてのEdge条件を正しくチェックしていることを注意深く確認する必要があります(例:テストに使用される同じまたはより少ない同じインデックスではなく、インデックス作成など)。

56
Lewis Pringle
int i = 0;
while (i < array.length && array[i] != value)
    i++;
return i < array.length;

[…]構造化プログラミングの方法で、すべてがより明白になり、moreになります。

結構です。変数iは、ここでwhileループの外側に存在し、したがって外部スコープの一部ですが、xループの(意図された)forは、ループ。スコープは、プログラミングに構造を導入する非常に重要な方法の1つです。

9
null

3つ目のオプションをまとめて提案します。

return array.find(value);

配列を反復処理する理由は多数あります。特定の値が存在するかどうかを確認し、配列を別の配列に変換し、集計値を計算し、配列からいくつかの値をフィルター処理します...単純なforループを使用する場合、不明確です特にforループの使用方法が一目でわかります。ただし、最新の言語のほとんどは、配列データ構造に豊富なAPIがあり、これらの異なる意図が非常に明確になります。

Forループを使用して、ある配列を別の配列に変換することを比較します。

int[] doubledArray = new int[array.length];
for (int i = 0; i < array.length; i++) {
  doubledArray[i] = array[i] * 2;
}

そして、JavaScriptスタイルのmap関数を使用します。

array.map((value) => value * 2);

または配列を合計します:

int sum = 0;
for (int i = 0; i < array.length; i++) {
  sum += array[i];
}

対:

array.reduce(
  (sum, nextValue) => sum + nextValue,
  0
);

これが何をするか理解するのにどれくらいかかりますか?

int[] newArray = new int[array.length];
int numValuesAdded = 0;

for (int i = 0; i < array.length; i++) {
  if (array[i] >= 0) {
    newArray[numValuesAdded] = array[i];
    numValuesAdded++;
  }
}

versus

array.filter((value) => (value >= 0));

3つのケースすべてで、forループは確実に読み取り可能ですが、forループがどのように使用されているかを理解し、すべてのカウンターと終了条件が正しいことを確認するために少し時間を費やす必要があります。最新のラムダスタイルの関数はループの目的を非常に明確にし、呼び出されているAPI関数が正しく実装されていることは確かです。

JavaScriptRubyC# 、および Java を含む最新の言語のほとんどは、配列との関数型の相互作用のこのスタイルを使用しますおよび同様のコレクション。

一般に、forループを使用することは必ずしも間違っているとは思いませんが、個人的な好みの問題ですが、配列を使用するこのスタイルの使用を強く支持しています。これは特に、各ループが何をしているかを決定する際の明確さが増したためです。言語の標準ライブラリに同様の機能やツールがある場合は、このスタイルの採用も検討することをお勧めします。

2
Kevin

2つのループのセマンティクスは異なります。

  • 最初のループは単純な「はい/いいえ」の質問に答えます。「配列には、探しているオブジェクトが含まれていますか?」それは可能な限り短い方法で行われます。

  • 2番目のループは質問に答えます。「配列に探しているオブジェクトが含まれている場合、最初に一致したインデックスは何ですか?」繰り返しますが、それは可能な限り短い方法で行われます。

2番目の質問への回答は最初の質問への回答よりも厳密に多くの情報を提供するため、2番目の質問に回答してから最初の質問の回答を導き出すことができます。とにかく、それがreturn i < array.length;行です。

既存のより柔軟なツールを再利用できない限り、通常は目的に合ったツールを使用するが最善であると思います。つまり:

  • ループの最初のバリアントを使用しても問題ありません。
  • 最初のバリアントを変更して、bool変数とブレークを設定するだけでもかまいません。 (2番目のreturnステートメントを回避し、関数の戻り値ではなく変数で答えを取得できます。)
  • std::findの使用は問題ありません(コードの再利用!)。
  • ただし、検索を明示的にコーディングしてからboolへの回答を減らすことはできません。