8

Java で sを使用することは悪い習慣と見なされているとよく耳にbreakしますが、スタック オーバーフローに関するいくつかのスレッドを読んだ後、そうではないことがわかりました。多くの人は、場合によっては許容できると言っています。

この場合、何が悪い習慣であるか、そうでないかについて少し混乱しています。

Project Euler : 問題 7 の場合、以下のコードを作成しました。課題は、10001 番目の素数を見つけることでした。

int index = 2, count = 1, numPrime = 1;

while (true) {
    index++;

    if (isPrime(index)) {
        count = index;
        numPrime++;
    }

    if (numPrime >= 10001)
        break;
}

System.out.println(count);

これは (21ms で) 正しい答えを返しますが、重大な警告を見落としているのでしょうか? ブレークなしで while ループを作成することは 100% 可能ですが、これを実行する方が少し簡単であることがわかります。

break;私が悪い習慣を使用する方法ですか?常に使用する方法があることは知っていますが、ここでは本当にひどいのでしょうか?

どうもありがとう

ユスティアン

編集

これが私の isPrime() コードです。私はそれをしている間にこれを最適化することもできます。

public static boolean isPrime(long num) {  
    if (num == 2)
        return true;

    if (num % 2 == 0 || num <= 0)
        return false;

    for (long i = 3; i * i <= num; i += 2)
        if (num % i == 0)
            return false;

    return true;
}
4

9 に答える 9

22

一般的に休憩が悪い習慣かどうかはわかりませんが、これは悪い習慣だと思います。

不要なのでちょっとばかげています。Yourwhile (true) ... breakは次と完全に同等です。

while (numPrime < 10001) {
   ...
}

しかし、はるかに直感的ではありません。

物事を表現する標準的な方法に固執することは、読者の計算オーバーヘッドが少なくなることを意味し、コードが理解しやすくなるため、保守が容易になり、最終的にはより堅牢になります。

編集(コメントへの回答):常に1つを使用する方法があることは正しいですが、ルール(必要な場合)は比較的単純です。最も読みやすいものを書きます. あなたが投稿した場合、これは最も読みやすいものではありません. 場合によっては、一致する特定の候補が見つかるまでコレクションをループするなど、このパターンを表現する最も適切な方法break、おそらく使用することです。

break変数をいつ使用し、いつ変数を取り出して代わりに条件を使用するかについて、厳格なルールを考え出すことは困難です (そして、私は無益だと主張します) 。多くの場合、何が最も単純なオプションかを判断するのは簡単ですが、常にそうであるとは限りません。これは、経験が本当に重要な例の 1 つです。自分で読んだり書いたりした良いコードと悪いコードが多ければ多いほど、良い例と悪い例の個人ライブラリが大きくなり、「最良のコード」を簡単に判断できるようになります。 「*与えられた概念を表現する方法は.

※もちろん主観です!

于 2010-07-30T16:11:20.963 に答える
15

whileこの場合、条件を変更するだけで簡単になるように思えます。

while (numPrime < 10001) {

while(true)これは通常、ループが次で終了する場合に当てはまります。

if (condition)
{
    break;
}

...ループの本体の他のものが実行されるかどうかを確認する必要がありますがcontinue.

または、少し再構築することもできます。

int current = 1;
for (int i = 0; i < 10001; i++)
{
    current++;
    while (!isPrime(current))
    {
        current++;
    }
}

そしたらcurrent最後に答えが出ます。

for何かを特定の回数実行しようとしている場合、私は通常、while ループよりもループを好みます。この場合、「何か」は「次の素数を見つける」です。

プログラミングには、「メソッドへの 1 つの出口点」や「ブレークを使用しない」など、行き過ぎていると感じるドグマがいくつかあります。できるだけ読みやすいコードを書いてください。いくつかのコードを見て、何が起こっているのかが盲目的に明白ではないと感じた場合は、それを構造化する別の方法を試してみてください。場合によっては、ループを変更する場合があります。メソッドを抽出している場合もあります。場合によっては、いくつかのロジックが反転しています (最初に負の分岐に対処し、おそらく早期に終了してから、通常のケースを処理します)。

于 2010-07-30T16:12:01.757 に答える
2

休憩なしで逃げることができるなら、それをしてください。

行う

 while (numPrime < 10001) ...
于 2010-07-30T16:12:34.483 に答える
2

これまで言われてきたことと大差はありませんが、読みやすさ、透過的なロジックの観点からお勧めします

long i,primes=0;
for (i=2;primes<10001;i++) {
    if (isPrime(i))
        primes++;
}

私が答えです。

于 2010-07-30T19:45:50.517 に答える
2

それが do/while が発明された理由です:

do {
//...
} while(numPrime < 10001);

それwhile(true)は私が悪い習慣を見つけたビットであり、もちろんbreak.

于 2010-07-30T16:14:14.500 に答える
2

ブレークを使用することが理にかなっている場合、いくつかの条件があります。1 つは、N.5 ループを実行する必要がある場合です。つまり、ループを数回実行しますが、最後の回は常にループ本体の途中で実行します。この場合、ブレークの使用を避けることができますが、そうするとコードが難読化されたり、重複が発生したりすることがよくあります。例えば:

while (true) {
    first part;
    if (finished) 
       break;
    second part;
}

次のようなものに変えることができます:

first part;
while (!finished) {
    second part;
    first part;
}

また:

while (!finished) {
    first part;
    if (!finished)
        second part;
}

これらのいずれも、必ずしも大きな改善ではありません。ブレークが意味を持つもう 1 つの状況は、単純にエラーのようなものを処理する場合です。たとえば、処理する N 個のファイルが渡された場合、そのうちの 1 つが開かなかった場合にループから抜け出すのは理にかなっています。それにもかかわらず、それがまったく合理的である場合、ループの条件で明示的に述べられたループから抜け出す条件を持つ方が明らかに良いです。

于 2010-07-30T16:25:45.707 に答える
0

ループを終了できる条件が 1 つしかなく、条件をチェックする前に実行する必要があるコードがそれほど多くなく、条件をチェックした後に実行する必要があるコードがそれほど多くない場合、条件をループに入れます。

確かに「do{}while(1);」の用途はあると思います。ループ。その中で:

  1. ループ継続の場合、主な条件は、条件自体にコードを配置するのがせいぜい厄介であることを確認する前と後に、十分なコードを実行する必要があります。
  2. ループには複数の終了条件があり、最も論理的にループの一番上または一番下に位置する終了条件は、他の終了条件に対して実行されるべきではない特別なコードを実行する必要があります。

フラグを使用して終了条件を区別することを好む人もいます。私は、このようなフラグを、プログラムのアクションを実際に最もよく具体化するコーディング構造を回避するための努力と見なす傾向があります。

速度をあまり気にしない場合は、任意の形式のgoto、 premature-exit returnを使用することを避けることができます。さらに言えば、複数のwhileを使用することも、単純な条件で使用することも避けることができます。プログラム全体をステート マシンとして単純に記述します。

void do_whatever(ボイド)
{
  int current_state=1;
  行う
  {
    次の状態 = 0;
    もし (現在の状態 == 1)
    {
      do_some_stuff();
      次の状態 = 2;
    }
    もし (現在の状態 == 2)
    {
      do_some_more_stuff();
      次の状態 = 3;
    }
    ...
    現在の状態 = 次の状態;
  } while(現在の状態);
}

このようなコーディングが役立つ場合があります (特に、「while」を do_whatever() ルーチンからループに引き出して、いくつかの同様にコーディングされたルーチンを「同時に」実行できる場合)。また、「goto」などを使用する必要はありません。しかし、読みやすくするためには、構造化プログラミング構造の方がはるかに優れています。

私の考えでは、フラグを使用してループを終了し、終了の原因に基づいて実行するコードをいくつか選択することは、構造化コードを非構造化コードに置き換えることです。ループ内の場合、私は書きます

  if (index >= numItems)
  {
    createNewItem(インデックス);
    壊す;
  }

新しいアイテムを作成する理由は、インデックスがアイテムの数を超えたためであり、条件を重複してテストする必要がないことがすぐに (そしてローカルで) 明らかです。代わりに、何かを見つけるかアイテムがなくなるまでループする場合は、ループの後に条件を冗長にテストするか、すべてのループ反復にフラグ テストを追加する必要があります。

于 2010-07-30T16:58:27.323 に答える
0

他の人が指摘したように、テストを WILE に簡単に移動できるため、あなたが示した例は悪いものです。

ブレークを使用するケースは、これが最後のループであることがわかる前に何らかの処理を行わなければならないループがある場合です。例えば:

while (true)
{
  String inLine=inStream.readLine();
  if (inLine.startsWith("End"))
    break;
  ... process line ...
}

この例は少し不自然です: ファイルまたは他のソースからいくつかのデータを読み取り、このデータを何らかの方法で解析してから、現在処理しようとしているものの最後に到達したことを知るという問題に遭遇することがよくあります。 .

もちろん、次のように、行を読み取り、必要な解析を行う関数を作成することもできます。

while (!endOfInterestingData(inStream))
{
  ... process ...
}

ただし、関数とループの本体の両方が読み取ったデータにアクセスする必要があり、関数はループ処理を制御するためにブール値を返さなければならないため、データを返すことができないという問題が発生する可能性があります。ループ本体で利用可能なデータは、関数がそれを相互にアクセス可能なフィールドにスローすることです。これにより、ループがどこからデータを取得するかがわかりにくくなります。

于 2010-07-30T16:59:22.927 に答える
0

このソリューションは非常に高速です。

#include <stdio.h>

unsigned long isqrt(unsigned long n) {
    // http://snippets.dzone.com/posts/show/2715
    unsigned long a;
    for (a = 0; n >= (2*a)+1; n -= (2*a++) + 1);
    return a;
}

void nPrimes(const long N, long *const primes)
{
    unsigned long n, count, i, root;

    primes[0] = 2;
    count = 1;

    for (n = 3; count < N; n+=2) {

        root = isqrt(n);

        for (i = 0; primes[i] <= root; i++) {
            if ((n % primes[i]) == 0) goto notPrime;
        }
/*      printf("Prime #%lu is: %lu\n", count+1, n);*/
        primes[count++] = n;

        notPrime: ;
    }
}

int main (int argc, char **argv)
{
    long N;

    if (argc > 1) {
        N = atoi(argv[1]);
    } else {
        N = 10001;
    }

    long primes[N];

    nPrimes(N, primes);

    printf("Prime #%lu is: %lu\n", N, primes[N-1]);
}

ノート:

  • isqrt(n) は floor(sqrt(n)) です。これにより、とにかく必要のない浮動小数点演算が回避されます。http://snippets.dzone.com/posts/show/2715のアルゴリズム。
  • nPrimes は素数のリストを保持します。これにより、素数の約数のみをテストできるようになり、コストのかかる mod 操作の大部分が省略されます。
  • 素数でさえ isqrt(n) までしかテストされません。
  • これは醜い goto ですが、C には名前付きの継続がなく、フラグ変数は完全に時間の無駄でした。
  • 素数配列は 2 で初期化され、3 からの奇数のみをチェックします。3 の倍数を削除することも同様に実行可能ですが、些細な複雑さではありません。
  • isqrt 呼び出しは、素数の 2 乗のテーブルを保持することで排除できます。これはほぼ間違いなくやり過ぎですが、必要に応じて実行できます。
  • 私の新しいマシンでは、これは瞬く間に実行されます。
于 2010-07-31T18:03:51.507 に答える