Я слышал, что некоторые перерывы - неплохая практика. Что насчет этого?

Я часто слышал, что использование breaks в Java считается плохой практикой, но, прочитав некоторые темы на 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 мс), но не упустил ли я серьезное предупреждение? На 100% возможно создать цикл while без перерыва, но я считаю, что это немного легче выполнить.

Я использую 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;
}

person Justian Meyer    schedule 30.07.2010    source источник
comment
Мой второй ужасный вопрос подряд на этой неделе. Надеюсь, не все плохое состоит из троек.   -  person Justian Meyer    schedule 30.07.2010
comment
Кстати, вы пропускаете простое число 2 в приведенном выше коде.   -  person aepryus    schedule 30.07.2010
comment
@aepryus: Я знаю, что это не 10001-е простое число, так зачем беспокоиться? Это просто еще одно число, которое нужно разобрать;). Конечно, это не экономит много времени, но в данном случае это не важно. Если бы я создал метод, запрашивающий n-е простое число, это было бы важно.   -  person Justian Meyer    schedule 30.07.2010
comment
Я просто надеюсь, что не стану медленно навешивать на себя ярлык одного из тех грязных программистов на Java. Я стараюсь изо всех сил с тем ограниченным классом, который у меня есть. Надеюсь, мои классы C в колледже помогут мне поправиться.   -  person Justian Meyer    schedule 30.07.2010
comment
Вы считаете простые числа. Эта функция не остановится, пока вы не найдете 10 001 простое число. Если вы пропустите 2, эта функция вернет 10 002-е простое число.   -  person aepryus    schedule 30.07.2010
comment
@aepryus: Я уже упоминал, что получаю правильный результат - проблема не в этом. Пожалуйста, просмотрите код дальше. Это как сказать for (int i = 1; i <= 10001; i++) { вместо более распространенного for (int i = 0; i < 10001; i++) {.   -  person Justian Meyer    schedule 30.07.2010
comment
@Justian Meyer: ИМХО, это плохая оптимизация. Вы пропускаете номер, который проверяется быстрее всего, и в то же время делаете код настолько запутанным, что вы запутали здесь нескольких других людей. Старайтесь избегать хитрых уловок - делайте все проще.   -  person Mark Byers    schedule 30.07.2010
comment
К вашему сведению, вы можете значительно ускорить код, автоматически пропуская числа, делящиеся на 2 и 3. Увеличивайте индекс на 6 при каждом запуске и тестируйте index + 1 и index + 5. Или просто автоматически пропускайте четные числа. И, во имя всего святого, убедитесь, что isPrime проверяет только простые числа, меньшие или равные sqrt (index).   -  person Thom Smith    schedule 31.07.2010
comment
@Thom Smith: Я безуспешно пытался пропустить эти числа - мой код стал слишком беспорядочным, но я знаю, что это лучше всего. Я опубликую свой код isPrime () выше. Я использую только квадратный корень.   -  person Justian Meyer    schedule 31.07.2010
comment
Вы можете сделать это на много порядков быстрее. Выложу пример.   -  person Thom Smith    schedule 31.07.2010


Ответы (9)


В этом случае мне кажется, что было бы проще просто изменить условие 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, когда вы пытаетесь сделать что-то определенное количество раз. В этом случае «что-то» - это «найти следующее простое число».

В программировании есть различные догмы, которые, как мне кажется, зашли слишком далеко, в том числе «одна точка выхода для метода» и «не использовать прерывание». Пишите код максимально разборчиво. Если вы смотрите на какой-то код и чувствуете, что то, что происходит, не слишком очевидно, попробуйте разработать другие способы его структурирования. Иногда это случай изменения цикла; иногда извлекает метод; иногда это инвертирует некоторую логику (сначала обработайте отрицательную ветвь, возможно, преждевременно завершите, а затем обработайте нормальный случай).

person Jon Skeet    schedule 30.07.2010
comment
Я поставил Джону эту точку зрения, потому что он не только предложил хорошее for решение, но и его объяснение хорошей / плохой практики было более определенным, чем у Анджея. - person Justian Meyer; 30.07.2010
comment
+1 по той же причине, что утверждает Юстиан. (А может быть, просто потому, что у меня еще нет значка «Народник» ... ;-)). Вы уловили догматический подход к этим вещам, и я никогда не понимал, почему некоторые люди упускают из виду большую читаемость деревьев соответствия Процессу. - person Andrzej Doyle; 30.07.2010
comment
Как отмечено в ответе Аджея, while(true), заканчивающийся на break, лучше подходит для конструкции do / while. - person sje397; 30.07.2010

Я не уверен, что перерывы вообще плохая практика, но думаю, что это так.

Это немного глупо, потому что в этом нет необходимости. Ваш while (true) ... break полностью эквивалентен:

while (numPrime < 10001) {
   ...
}

но гораздо менее интуитивно понятный.

Соблюдение канонического способа представления вещей означает меньшие вычислительные затраты для ваших читателей, упрощает понимание кода, а значит, упрощает обслуживание и в конечном итоге делает его более надежным.

Изменить (в ответ на комментарий): вы правы, что всегда есть способ использовать один, но правило (если вы хотите его) относительно простое: пишите то, что наиболее читается. В случае, который вы опубликовали, это не самый читаемый вариант, поскольку я дал альтернативу каноническим способом представить это. В некоторых случаях, например, в цикле по коллекции до тех пор, пока вы не найдете подходящего кандидата, использование break, вероятно, является наиболее подходящим способом представления этого шаблона.

Было бы сложно (и я буду спорить, бесполезно) пытаться придумать жесткие правила о том, когда использовать break, а когда извлекать переменные и использовать вместо них условные выражения. Часто легко сказать, какой вариант самый простой, но не всегда. Это один из тех примеров, когда действительно имеет значение опыт - чем больше хорошего / плохого кода вы прочитали и написали сами, тем больше ваша личная библиотека хороших и плохих примеров и тем легче вам будет сказать, какой из них «лучший». "* способ представить данную концепцию.

* Конечно субъективно!

person Andrzej Doyle    schedule 30.07.2010
comment
Абсолютно. На мою козу не так много всего, как while(true) - person sje397; 30.07.2010
comment
@Andrzej Doyle: Я действительно только что добавил это в пример: /. Надо было выбрать лучший. - person Justian Meyer; 30.07.2010
comment
Действительно, он не передает никакой информации о том, что делается в цикле. В очень редких случаях это действительно так, если что-то предназначено для бесконечного цикла, но даже в этом случае вы обычно должны иметь вместо этого флаг на основе while(!shutdown). - person Andrzej Doyle; 30.07.2010
comment
Мне нравится использовать while (1+1+1 != 2) или что-то подобное, просто чтобы сбить с толку людей. - person Blorgbeard; 30.07.2010
comment
@Justian: Надеюсь, редактирование в какой-то степени погасит мои (по общему признанию, чрезмерные) голоса за. - person Andrzej Doyle; 30.07.2010
comment
Хорошие ответы на простые вопросы всегда вызывают больше всего откликов, многие люди понимают вопрос и соглашаются с тем, что он правильный :) - person Affe; 30.07.2010
comment
Не совсем эквивалентно, так как в этом случае для оценки условия требуется JMP в начале цикла, тогда как break (или, лучше, do / while) этого не делает. - person sje397; 30.07.2010
comment
@ sje397: Плата фи фо фум, я чувствую запах крови преждевременной оптимизации ... (править) Я бы с радостью пожертвовал одной инструкцией процессора, чтобы мой исходный код был более читабельным. - person Andrzej Doyle; 30.07.2010
comment
@ sje397: В выходных данных компилятора, которые я видел, они часто помещают условие цикла while в конец цикла и входят в цикл, перескакивая на него. - person Nefrubyr; 30.07.2010
comment
@Andrzej Doyle - абсолютно нет. Я ищу эквивалентность исходному коду, удобочитаемость и причину, по которой чья-то интуиция может заставить их писать таким образом. Это именно, для чего нужен do / while. Не говоря уже о том, что такая оптимизация уместна при вычислении больших простых чисел :) - person sje397; 30.07.2010
comment
Для эквивалентности я был бы доволен функциональной эквивалентностью, а не идентичностью результирующего байт-кода / машинного кода. И хорошо, я понимаю вашу точку зрения, но в подавляющем ситуациях один код операции не имеет значения. Если ваша программа проводит всю свою жизнь в цикле, тогда вы правы, но я считаю, что вам следовало бы до такой степени квалифицировать свой комментарий. - person Andrzej Doyle; 30.07.2010
comment
Это по-прежнему в основном эквивалентность в удобочитаемости и значении, которая заставляет меня думать, что здесь более уместно делать / пока. Он всегда выполняет код внутри цикла, по крайней мере, один раз. В конце он проверяет условие. Это do / while. - person sje397; 30.07.2010
comment
Вы правы, и я согласен с вашей оценкой, но я считаю важным, что do / while - необычная конструкция, и разработчики привыкли видеть условие цикла в верхней части цикла, а не в конце. Следовательно, я бы использовал do / while только в том случае, если был шанс, что условие могло быть ложным на первой итерации, в противном случае поведение идентично. У вас есть точка зрения, что в abstract do/while здесь более естественно, но на практике я бы считал простое while каноническим представлением. - person Andrzej Doyle; 30.07.2010

если вы можете уйти без перерыва, то сделайте это.

do

 while (numPrime < 10001) ...
person hvgotcodes    schedule 30.07.2010

Есть несколько условий, при которых имеет смысл использовать перерыв. Один из них - когда вам нужно выполнить цикл N.5, то есть вы выполните цикл несколько раз, но в последний раз вы всегда будете выполнять где-то в середине тела цикла. В этом случае можно избежать использования разрыва, но это часто приводит к запутыванию кода или дублированию. Например:

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

можно превратить во что-то вроде:

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

or:

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

Ни то, ни другое не обязательно является серьезным улучшением. Другое обстоятельство, при котором перерыв может иметь смысл, - это просто иметь дело с чем-то вроде ошибки. Например, если вам передано N файлов для обработки, может иметь смысл выйти из цикла, если один из них не открывается. Тем не менее, когда это вообще разумно, явно лучше иметь условие, при котором вы будете выходить из цикла, явно указанное в условии цикла.

person Jerry Coffin    schedule 30.07.2010
comment
Уловка вашего второго примера в том, что первая часть теперь написана дважды. Если вы можете поместить все это в функцию, которая просто вызывается дважды, это не так уж и плохо, но если нет, что ж, каждый раз, когда у вас есть один и тот же блок кода дважды, рано или поздно кто-то внесет изменение в один случай, а не другой, и теперь у вас беспорядок. - person Jay; 30.07.2010

Вот для чего был изобретен do / while:

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

Это while(true) бит, который я считаю плохой практикой, что, конечно же, приводит к break.

person sje397    schedule 30.07.2010

Не сильно отличается от всего, что было сказано ранее, но с точки зрения удобочитаемости и прозрачной логики я бы порекомендовал

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

я - ответ.

person aepryus    schedule 30.07.2010

Если есть только одно условие, при котором цикл может завершиться, и нет большого количества кода, который нужно запустить перед проверкой условия, и нет большого количества кода, который нужно запустить после проверки условия , поместите условие в цикл.

Я действительно думаю, что "do {} while (1);" определенно можно использовать; петля. Из их:

  1. In the loop-continue case, the primary condition needs to have enough code run both before and after it is checked that putting the code in the condition itself would be awkward at best.
  2. The loop has multiple exit conditions, and the exit condition which would most logically sit at the top or bottom of the loop would require special code to execute which should not execute for other exit conditions.

Некоторым людям нравится использовать флаги для обозначения условий выхода. Я склонен рассматривать такие флаги как попытку избежать структур кодирования, которые на самом деле лучше всего воплощают действие программы.

Если вас не слишком заботит скорость, можно никогда не использовать любую форму goto, преждевременного выхода return или, если на то пошло, использовать более одного 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;
  }

сразу (и локально) ясно, что причина, по которой я создаю новый элемент, заключается в том, что мой индекс превышает количество элементов, и мне не нужно повторно проверять условие. Если вместо этого я буду повторять цикл до тех пор, пока что-то не найду или не закончатся элементы, тогда мне придется либо повторно проверять условие после цикла, либо добавлять тест флага к каждой итерации цикла.

person supercat    schedule 30.07.2010

Как отмечали другие, приведенный вами пример плох, потому что вы можете легко перенести тест в WHILE.

Случаи, когда я использую перерыв, - это когда у меня есть цикл, в котором я должен выполнить некоторую обработку, прежде чем я узнаю, что это последний раз. Например:

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

Этот пример лишь слегка надуманный: я часто сталкиваюсь с проблемами, когда я читаю некоторые данные из файла или другого источника, каким-то образом анализирую эти данные и только потом знаю, что я достиг конца того, что мне интересно обрабатывать прямо сейчас. .

Вы, конечно, можете написать функцию, которая читает строку и выполняет необходимый синтаксический анализ, например:

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

Но тогда у вас может возникнуть проблема, заключающаяся в том, что и функция, и тело цикла нуждаются в доступе к считанным данным, а функция должна возвращать логическое значение для обработки цикла управления, чтобы она не могла вернуть данные, поэтому единственный способ сделать данные, доступные для тела цикла, заключаются в том, чтобы функция перебросила их в какое-то взаимно доступное поле, которое затем скрывает, откуда цикл получает свои данные.

person Jay    schedule 30.07.2010

Это решение довольно быстрое:

#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) - пол (sqrt (n)). Это позволяет избежать операций с плавающей запятой, которые нам в любом случае не нужны. Алгоритм из http://snippets.dzone.com/posts/show/2715.
  • nPrimes хранит список простых чисел. Это позволяет вам тестировать только простые делители, что исключает большинство дорогостоящих операций с модификациями.
  • Даже простые числа проверяются только до isqrt (n).
  • Это уродливый goto, но C не имеет имени continue, и переменная флага была бы пустой тратой времени.
  • Массив простых чисел инициализируется значением 2, и он проверяет только нечетные числа из 3. Удаление числа, кратного 3, аналогично выполнимо, но не тривиальной сложности.
  • Вызов isqrt можно исключить, если вести таблицу квадратов простых чисел. Это почти наверняка перебор, но вы можете, если хотите.
  • На моей не новой машине это происходит в мгновение ока.
person Thom Smith    schedule 31.07.2010
comment
Я проведу несколько тестов скорости с моим методом против вашего и опубликую результаты в ближайшее время, если не завтра. ... Если подумать, скорее всего, это произойдет завтра, так как мне нужно перевести это на Java. - person Justian Meyer; 31.07.2010