我听说有些“休息”并不是不好的做法。这个如何?

时间:2010-07-30 16:09:33

标签: java primes break

我经常听说在Java中使用break被认为是不好的做法,但是在阅读Stack Overflow上的一些线程之后,我已经看到了。许多人说在某些情况下这是可以接受的。

在这种情况下,对于什么是/不是坏习惯,我有点困惑。

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

这会返回正确答案(21毫秒),但我是否忽略了严重的警告?创造一个没有休息的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;
}

9 个答案:

答案 0 :(得分:22)

我不确定一般的休息是不好的做法,但我认为这个是。

这有点傻,因为这是不必要的。您的while (true) ... break完全等同于:

while (numPrime < 10001) {
   ...
}

但更不直观。

坚持使用规范的方式来表示事物意味着您的读者可以减少计算开销,使您的代码更易于理解,从而更易于维护并最终更加健壮。

编辑(回应评论):你是对的,总是有办法使用一个,但规则(如果你想要的话)相对简单:写任何最可读的。在你发布的情况下,这不是最具可读性的,因为我给出的替代方案是用于表示这一点的规范方式。在某些情况下,例如通过集合循环直到找到匹配的特定候选项,使用break可能 是表示此模式的最合适方式。

尝试提出关于何时使用break以及何时提取变量和使用条件的硬性规则将是困难的(并且我认为是徒劳的)。通常很容易分辨出最简单的选择,但并非总是如此。这是经验真正重要的例子之一 - 你自己阅读和编写的代码越多越好,你的个人图书馆的好的和坏的例子就越大,你就越容易说出“最好的” “*表示给定概念的方式是。

*当然这是主观的!

答案 1 :(得分: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将是最后的答案。

当您尝试执行特定次数的操作时,我通常更喜欢在{while}循环中使用for循环。在这种情况下,“某事”是“找到下一个素数”。

编程中有各种各样的教条,我觉得这些教条太过分了 - 包括“方法的一个出口点”和“不要使用中断”。尽可能简单地编写代码。如果你看一些代码并觉得它不是很明显地发生了什么,试着找出构建它的其他方法。有时这是一个改变循环的情况;有时它正在提取一种方法;有时它会反转一些逻辑(首先处理负分支,可能会提前退出,然后处理正常情况)。

答案 2 :(得分:3)

使用休息时有几个条件是有意义的。一个是当你需要执行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;
}

这些都不一定是重大改进。休息可能有意义的另一种情况就是处理类似错误的事情。例如,如果您已经传递了N个文件进行处理,那么如果其中一个文件无法打开,则可能会突破循环。尽管如此,当它完全合理时,显然更好的是你将退出循环条件中明确陈述的循环。

答案 3 :(得分:2)

如果你可以不间断地离开,那就去做吧。

 while (numPrime < 10001) ...

答案 4 :(得分:2)

这就是/为什么发明的:

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

while(true)位是我发现不好的做法,当然会导致break

答案 5 :(得分:2)

与以前所说的任何事情都没有太大的不同,但是从可读性,透明的逻辑观点来看,我会建议

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

我就是答案。

答案 6 :(得分:0)

如果只有一个条件可以退出循环,并且在检查条件之前没有需要运行的大量代码,并且没有大量代码需要在之后运行检查条件,将条件置于循环中。

我确实认为“do {}有一定用途,而(1);”环。其中:

  1. 在循环继续的情况下,主要条件需要在检查之前和之后运行足够的代码,将代码置于条件本身中最多是尴尬的。
  2. 循环有多个退出条件,退出条件最逻辑地位于循环的顶部或底部,需要执行特殊代码,而不应执行其他退出条件。

有些人喜欢用旗帜来区分退出条件。我倾向于将这些标志视为努力避免编码结构,这实际上最能体现程序的作用。

如果一个人不太关心速度,可以避免使用任何形式的 goto ,提前退出返回,或者就此而言使用多个 while - 并且条件简单。只需将整个程序写为状态机:

void do_whatever(void)
{
  int current_state=1;
  do
  {
    next_state = 0;
    if (current_state == 1)
    {
      do_some_stuff();
      next_state = 2;
    }
    if (current_state == 2)
    {
      do_some_more_stuff();
      next_state = 3;
    }
    ...
    current_state = next_state;
  } while(current_state);
}

有时这种编码很有用(特别是如果“while”可以从do_whatever()例程中拉出到一个循环中,该循环“同时”运行几个类似编码的例程)。并且它永远不必使用像“goto”这样的东西。但是为了便于阅读,结构化编程构造要好得多。

在我看来,使用一个标志来退出一个循环,然后根据退出原因选择要执行的几段代码之一就是用非结构化代码替换结构化代码。如果在循环中我写

  if (index >= numItems)
  {
    createNewItem(index);
    break;
  }

它立即(并且在本地)清楚我创建新项目的原因是因为我的索引超过了项目数,并且我不需要冗余地测试条件。如果我循环直到我发现了某些东西或者用完了项目,那么我要么必须在循环之后冗余地测试条件,要么在每次循环迭代中添加一个标记测试。

答案 7 :(得分:0)

正如其他人所说,你提供的例子很糟糕,因为你可以轻松地将测试移到WHILE。

我使用休息的情况是我有一个循环,我必须做一些处理才知道这是最后一次。例如:

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

这个例子只是略有设计:我经常遇到问题,我从文件或其他来源读取一些数据,以某种方式解析这些数据,然后才知道我已经达到我感兴趣的结果现在处理。

当然,您可以使用一个函数来读取行并进行必要的解析,例如:

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

但是你可能会遇到问题,即循环的函数和体都需要访问数据读取,并且函数必须返回一个布尔来控制循环处理,因此它不能返回数据,所以只有使数据可用于循环体的方法是让函数将其抛入一些可相互访问的字段,然后模糊循环从其获取数据的位置。

答案 8 :(得分: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保留了素数列表。这允许您仅测试素数除数,从而减少绝大多数昂贵的模式操作。
  • 即使素数也只测试到isqrt(n)。
  • 这是一个丑陋的转到,但C没有命名继续,而标志变量将完全浪费时间。
  • 素数数组初始化为2,它只检查3中的奇数。消除3的倍数同样可行,但不具有微不足道的复杂性。
  • 可以通过保留素数的平方表来消除isqrt调用。这几乎肯定是矫枉过正的,但如果你愿意,你可以。
  • 在我的新机器上,这一切都在眨眼之间。