Круговой инкремент: Который “лучше”?

То, когда Вам представили кольцевой буфер как массив, и Вам нужен индекс для повторений (т.е. когда Вы достигаете максимально возможного индекса и увеличиваете его), является им "лучше" к:

return (++i == buffer.length) ? 0: i;

Или

return ++i % buffer.length;

Имеет использование оператора по модулю какие-либо недостатки? Действительно ли это менее читаемо, чем первое решение?

Править:

Конечно, это должно быть ++ я вместо меня ++, изменил это.

РЕДАКТИРОВАНИЕ 2:

Одно интересное примечание: Я нашел первую строку кода в реализации ArrayBlockingQueue Doug Lea.

6
задан helpermethod 15 May 2013 в 14:27
поделиться

10 ответов

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

Самая удобная для чтения версия следующая:

return (i == buffer.length-1) ? 0 : i+1;

Использование ++ добавляет ненужный побочный эффект к проверке (не говоря уже о том, что я твердо уверен, что вам следовало использовать вместо этого предварительное приращение)

В чем проблема с исходным кодом? Давайте посмотрим, не так ли?

return (i++ == N) ? 0 : i; // OP's original, slightly rewritten

Итак, мы знаем, что:

  • i - это post -увеличено, поэтому, когда i == N-1 перед return , это вернет N вместо немедленного перехода на 0
    • Это предназначено? В большинстве случаев цель состоит в том, чтобы использовать N как исключительную верхнюю границу
  • Имя переменной i предлагает локальную переменную по соглашению об именах, но это правда?
    • Из-за побочного эффекта нужно дважды проверить, является ли это полем

Для сравнения:

return (i == N-1) ? 0 : i+1; // proposed alternative

Здесь мы знаем, что:

  • i не изменяется, не имеет значения, локальная это переменная или поле
  • Когда i == N-1 , возвращаемое значение равно 0 , что является более типичным сценарием.

Подход %

В качестве альтернативы вы также можете использовать версию % следующим образом:

return (i+1) % N;

В чем проблема с % ? Проблема в том, что хотя большинство людей думают, что это оператор по модулю , это НЕ! Это оператор остатка ( JLS 15.17.3 ). Многие люди часто это путают.Вот классический пример:

boolean isOdd(int n) {
   return (n % 2) == 1; // does this work???
}

Этот код не работает !!! Он возвращает false для всех отрицательных значений! Проблема в том, что -1% 2 == -1 , хотя математически -1 = 1 (mod 2) .

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

См. Также

10
ответ дан 8 December 2019 в 03:00
поделиться

Разве версия i ++% buffer.length не имеет недостатка в том, что она продолжает увеличивать i, что может привести к достижению какого-то ограничения max_int / max_long / max_whatever?

Кроме того, я бы разделил это на

i = (i++ == buffer.length) ? 0 : i;
return i;

, иначе у вас, скорее всего, будет ошибка.

3
ответ дан 8 December 2019 в 03:00
поделиться

Не просите меня выбирать между двумя вариантами, оба из которых содержат постинкремент (*), смешанный с вычислением выражения. Я скажу «нет».

(*) Обновление: Позже исправили преинкремент.

5
ответ дан 8 December 2019 в 03:00
поделиться

Я думаю, что второе решение имеет явное преимущество в том, что оно работает, а первое - нет. Первое решение всегда будет возвращать ноль, когда i становится больше, чем buffer.length , потому что i никогда не сбрасывается.

Оператор по модулю не имеет недостатков.

2
ответ дан 8 December 2019 в 03:00
поделиться

Конечно, было бы более читабельно использовать if:

i++;
if (i >= buffer.length) 
{
  i = 0;
}
return i;

Немного зависит от того, изменится ли когда-либо длина buffer.length.

2
ответ дан 8 December 2019 в 03:00
поделиться

Лично я предпочитаю подход по модулю. Когда я вижу по модулю, я сразу думаю об ограничении диапазона и зацикливании, но когда я вижу тернарный оператор, я всегда хочу подумать об этом более внимательно просто потому, что есть больше терминов, на которые нужно смотреть. Тем не менее, удобочитаемость является субъективной, как вы уже указали в своих тегах, и я подозреваю, что большинство людей не согласится с моим мнением.

Однако производительность не субъективна. Modulo подразумевает операцию деления, которая часто выполняется медленнее, чем сравнение с нулем. Очевидно, это труднее определить в Java, поскольку мы не компилируем собственный код до тех пор, пока не возникнет дрожание.

Я бы посоветовал написать то, , которое вы сочтете наиболее подходящим (при условии, что это работает!) и попросите коллегу (если он у вас есть) оценить это. Если они не согласны, спросите другого коллегу - тогда проголосуйте большинством. #codingbydemocracy

1
ответ дан 8 December 2019 в 03:00
поделиться

Это очень субъективно и зависит от того, что привыкли видеть ваши коллеги. Я бы лично предпочел первый вариант, так как он явно выражает то, что делает код, т.е. если длина буфера достигнута, сброс на 0. Вам не нужно проводить математические вычисления или даже знать, что делает модуль (конечно, нужно! :)

.
1
ответ дан 8 December 2019 в 03:00
поделиться

Первый вызовет исключение ArrayIndexOutOfBoundsException, потому что i никогда не сбрасывается в 0.

Второй (вероятно) даст вам ошибку переполнения (или связанный с этим нежелательный эффект), когда i == Integer.MAX_VALUE (что может и не произойти в вашем случае, но это не хорошая практика, IMHO).

Итак, я бы сказал, что второй вариант «более правильный», но я бы использовал что-то вроде:

i = (i+1) % buffer.length;
return i;

В котором, как мне кажется, нет ни одной из двух проблем.

Я протестировал каждый код и с грустью обнаружил, что только один из предыдущих постов (на момент написания этого поста) работает. (Какой? Попробуйте все выяснить! Вы можете быть удивлены!)

public class asdf {

    static int i=0;
    static int[] buffer = {0,1,2};

    public static final void main(String args[]){
        for(int j=0; j<5; j++){
            System.out.println(buffer[getIndex()]);
        }
    }

    public static int getIndex(){
//      return (++i == buffer.length) ? 0: i;
//      return ++i % buffer.length;

//      i = (i++ == buffer.length) ? 0 : i;
//      return i;

//      i++;
//      if (i >= buffer.length) 
//      {
//        i = 0;
//      }
//      return i;

//      return (i+1 == buffer.length) ? 0 : i+1;

        i = (i+1) % buffer.length;
        return i;
    }

}

Ожидаемый результат: 1 2 0 1 {{ 1}} 2

Заранее приносим свои извинения, если с моей стороны произошла ошибка кода и я случайно кого-то оскорбил! xx

PS: +1 за предыдущий комментарий об отказе от постинкремента с проверками равенства (я пока не могу обновить сообщения = /)

3
ответ дан 8 December 2019 в 03:00
поделиться

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

Пример:

255 % 7 == 3

Таким образом, если вы используете байт (беззнаковый символ), например, когда число перекатывается после 255 (т. Е. Нуля), это не приведет к до 4. Должно получиться 4 (при 256% 7), поэтому он вращается правильно. Поэтому просто используйте конструкции тестирования ( if и тернарный оператор ) для проверки правильности


Если для достижения производительности, и , если число кратно 2 (т.е. 2 , 4, 8, 16, 32, 64, ...) используйте оператор и .

Таким образом, если длина буфера равна 16, используйте:

n & 15

Если длина буфера равна 64, используйте 63:

n & 63

Они вращаются правильно, даже если число возвращается к нулю. Между прочим, если число кратно 2, даже подход по модулю / остатку также будет соответствовать счету, т.е. он будет вращаться правильно. Но я могу предположить, что операция & быстрее, чем операция % .

3
ответ дан 8 December 2019 в 03:00
поделиться

Я предпочитаю оператор по модулю по той простой причине, что он короче. И любая программа должна иметь возможность мечтать по модулю, поскольку он почти так же распространен, как оператор плюс.

0
ответ дан 8 December 2019 в 03:00
поделиться
Другие вопросы по тегам:

Похожие вопросы: