Определение того, кратно ли число десяти или находится в пределах определенного набора диапазонов


104

У меня есть несколько циклов, которые мне нужны в моей программе. Я могу написать псевдокод, но я не совсем уверен, как писать их логически.

Я нуждаюсь -

if (num is a multiple of 10) { do this }

if (num is within 11-20, 31-40, 51-60, 71-80, 91-100) { do this }
else { do this } //this part is for 1-10, 21-30, 41-50, 61-70, 81-90

Это для настольной игры «Змеи и лестницы», если мой вопрос имеет больше смысла.

Я представляю себе первый оператор if, который мне нужно использовать по модулю. Было if (num == 100%10)бы правильно?

Второй я понятия не имею. Я могу написать это как if (num > 10 && num is < 21 || etc.), но должно быть что-то умнее этого.


16
Как правило, длина хорошего кода пропорциональна длине английского текста, описывающего то, что он делает. Поэтому, когда в вашей «спецификации» указано 11-20, 31-40, 51-60, 71-80, 91-100, вы можете ожидать, что ваш код также упомянет эти числа. Если эти числа откуда-то пришли или были сгенерированы по какой-то причине, попробуйте закодировать причину, а не цифры.
luqui

39
@ user3419168: Компилятор совершенно не заботится о читабельности вашего кода; он скомпилирует его за доли секунды. Но для людей, читающих ваш код, ваш выбор может привести к его пониманию за секунды, минуты, часы или никогда. Это требует затрат; людям платят за чтение и понимание кода, так что упростите им задачу. Всегда пишите производственный код, чтобы максимизировать читаемость, и помните, что краткость не обязательно улучшает работу кода.
Эрик Липперт

2
@AmadeusDrZaius - Я ОЧЕНЬ РЕДКО делал то же самое, но только для критических по производительности разделов. Подходит самый плотный цикл, который вызывается 100 миллионов раз, а оператор if в игре змей и лестниц - нет. Где провести грань между ними - это личный выбор.
Флорис

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

22
Это достойный вопрос, и я не хочу вообще ничего убирать из плаката, но это не заслуживает 500+ баллов. Вот как мы приходим к той чепухе, которую делаем с людьми с тысячами точек, которые здесь кажутся авторитетными. (Не стесняйтесь переместить этот комментарий, если он относится к
другому месту

Ответы:


87

Для первого, чтобы проверить, является ли число многократным использованием:

if (num % 10 == 0) // It's divisible by 10

Для второго:

if(((num - 1) / 10) % 2 == 1 && num <= 100)

Но это довольно сложно, и вам может быть лучше просто перечислить параметры явно.


Теперь, когда вы получили лучшее представление о том, что делаете, я бы написал вторую как:

   int getRow(int num) {
      return (num - 1) / 10;
   }

   if (getRow(num) % 2 == 0) {
   }

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


79
if((num - 1) / 10) % 2 == 1 && num < 100)- Я бы заплакал, если бы увидел это.
Даниэль Камил Козар

32
@DanielKamilKozar, как и следует.
Уинстон Эверт

2
@ user3419168, само по себе это заставляет задуматься о том, что вообще это значит. Он не дает никаких намеков на то, что он пытается сделать. Вот почему в редактировании я показал версию, которая разделяет логику на функцию, которая проясняет, что на самом деле делают вычисления.
Уинстон Эверт

3
Было бы разумно также утверждать, num >= 11что (1) нижний предел запрещен, и (2) %для отрицательного числа также возвращает отрицательное число. (Я должен признать, что использование & 1здесь «безопаснее», но также
требует

2
+1 для Edit, он попадает в почему из списка диапазонов и представляет его в удобочитаемой форме. ИМО, еще одним шагом было бы обернуть getRow(num) % 2 == 0функцию, чтобы было кристально ясно, какова цель. bool inEvenRow(int num){ return getRow(num) % 2 ==0;}
Мистер Миндор

40

if (число кратно 10) {сделай это}

if (num % 10 == 0) {
  // Do something
}

if (число находится в пределах 11-20, 31-40, 51-60, 71-80, 91-100) {сделайте это}

Уловка здесь состоит в том, чтобы найти некоторую общность между диапазонами. Конечно, вы всегда можете воспользоваться методом «грубой силы»:

if ((num > 10 && num <= 20) ||
    (num > 30 && num <= 40) ||
    (num > 50 && num <= 60) ||
    (num > 70 && num <= 80) ||
    (num > 90 && num <= 100)) {
  // Do something
}

Но вы можете заметить, что если вычесть 1из num, у вас будут диапазоны:

10-19, 30-39, 50-59, 70-79, 90-99

Другими словами, все двузначные числа, первая цифра которых нечетная. Затем вам нужно придумать формулу, которая это выражает. Вы можете получить первую цифру, разделив ее на 10, и проверить, что она нечетная, проверив остаток 1 при делении на 2. Собираем все вместе:

if ((num > 0) && (num <= 100) && (((num - 1) / 10) % 2 == 1)) {
  // Do something
}

Учитывая компромисс между более длинным, но поддерживаемым кодом и более коротким «умным» кодом, я бы каждый раз выбирал более длинный и ясный. По крайней мере, если вы попытаетесь быть умным, пожалуйста, включите комментарий, который точно объясняет, чего вы пытаетесь достичь.

Это помогает предположить, что следующий разработчик, который будет работать над кодом, вооружен и знает, где вы живете. :-)


7
Я бы по-прежнему выбрал умный код, но превратил бы его в поддерживаемый код, извлекая функции. Было бы так же удобно читать, если бы этот последний бит сказал && isTensDigitOdd(num), возможно, с комментарием перед определением функции, объясняющим, что он делает. Если такой шаблон существует, комментарий, объясняющий причины этого шаблона, поучителен для ремонтопригодности imo.
Крис

3
Крис, это отличная стратегия, когда «сообразительность» имеет явное преимущество: гораздо более короткий код (что означает меньшую вероятность опечатки, особенно если она изменяется) или значительное повышение эффективности. Практически всегда существует компромисс между краткостью, ясностью и эффективностью, и поиск хорошего компромисса - отличный навык, который нужно развивать. (См. Stackoverflow.com/a/2151844/29157 для смеха.)
Адам Лисс

1
Это гораздо лучший подход. Намного легче понять, чем «умный код», и разница в производительности, вероятно, незначительна.
user1477388

@AdamLiss, Да, мое мнение не имеет большого значения, так как у меня не было достаточно опыта, чтобы увидеть последствия этих решений. Я уверен, что скоро узнаю и при необходимости получу второе мнение.
Крис

1
Не продавайте себя слишком низко. Ваши инстинкты очень разумны, и вы, кажется, хотите продолжать учиться. Каждое мнение ценно, если за ним есть веская причина ... а иногда даже если нет. Готов поспорить деньгами, что ты далеко уйдешь.
Адам Лисс

30

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

switch(num)
{
case 11 ... 20:
case 31 ... 40:
case 51 ... 60:
case 71 ... 80:
case 91 ... 100:
    // Do something
    break;
default:
    // Do something else
    break;
}

1
Подскажите, пожалуйста, почему этот код не переносится?
M Sharath Hegde

8
@MSharathHegde, потому что для этого требуется расширение GCC, которое не является частью стандарта, и некоторые компиляторы его не поддерживают,
Брайан Чен

5
Это правильный ответ, потому что сразу видно, какова цель. Все эти «умные» ответы с модулем - кошмар обслуживания, даже с комментариями.
ухмыляющийся человек

@smirkingman Действительно, это то, что я сказал в своем комментарии к основному вопросу. Просто нужен некоторый опыт новых программистов в корпоративной работе, чтобы понять, что очевидный путь часто лучше, чем путь умного ниндзя.
Ричард Ле Мезурье

15

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

template<typename It, typename Elem>
bool in_any_interval(It first, It last, const Elem &val) {
    return std::any_of(first, last, [&val](const auto &p) {
        return p.first <= val && val <= p.second;
    });
}

Для простоты я использовал полиморфную лямбду (C ++ 14) вместо явного pairаргумента. Это также , вероятно , придерживаться использования <и ==чтобы соответствовать стандартным алгоритмам, но она работает , как это до тех пор , как Elemбыла <=определена для него. Во всяком случае, его можно использовать так:

std::pair<int, int> intervals[]{
    {11, 20}, {31, 40}, {51, 60}, {71, 80}, {91, 100}
};

const int num = 15;
std::cout << in_any_interval(std::begin(intervals), std::end(intervals), num);

Там есть живой пример здесь .


Аккуратное решение. Я бы, вероятно, использовал один массив, поскольку вы можете отформатировать его двумя числами в строке для представления пар.
Кевин Лам

@ HunterGuy2, очень хороший момент. На самом деле я собираюсь изменить его, чтобы он работал с парами, потому что по какой-то причине я думал только об итераторах zip.
Крис

Действительно хороший подход stl! Любить это!
Higuaro

5

Первый простой. Вам просто нужно применить оператор по модулю к вашему значению num:

if ( ( num % 10 ) == 0)

Поскольку C ++ оценивает каждое число, отличное от 0, как истинное, вы также можете написать:

if ( ! ( num % 10 ) )  // Does not have a residue when divided by 10

Что касается второго, я думаю, это будет понятнее:

Шаблон повторяется каждые 20, поэтому вы можете вычислить по модулю 20. Все элементы, которые вам нужны, будут в строке, кроме тех, которые делятся на 20.

Чтобы получить и их, просто используйте num-1 или лучше num + 19, чтобы не иметь дело с отрицательными числами.

if ( ( ( num + 19 ) % 20 ) > 9 )

Предполагается, что шаблон повторяется вечно, поэтому для 111-120 он будет применяться снова и так далее. В противном случае вам нужно ограничить числа до 100:

if ( ( ( ( num + 19 ) % 20 ) > 9 ) && ( num <= 100 ) )

5

С парой хороших комментариев в коде его можно написать довольно кратко и читабельно.

// Check if it's a multiple of 10
if (num % 10 == 0) { ... }

// Check for whether tens digit is zero or even (1-10, 21-30, ...)
if ((num / 10) % 2 == 0) { ... }
else { ... }

2
Первый комментарий не нужен. Любой программист с небольшим опытом знает, что num % 10 == 0это то же самое, что numи кратное 10.
Джастин

7
да, но новички тоже читают этот сайт. Обычно я бы не стал использовать этот комментарий в собственном коде, но он делает ответ более ясным для новичков, которым этот вопрос для начинающих будет полезен.
La-comadreja

2
Пожалуйста, никогда этого не делай. Фактически это снижает удобочитаемость, замедляя читателя и заставляя его читать все дважды. Любой программист, который этого не понимает, if (num % 10 == 0)означает то же самое, что // Check if it's a multiple of 10не должен поддерживать ваш код. Это всем известный антипаттерн.
Давуд ибн Карим

1
@DavidWallace см. Комментарий выше. Мы не можем гарантировать, что читатели этого поста будут знать этот антипаттерн.
La-comadreja

1
Нет, я имею в виду, что комментирование каждой строки, чтобы сказать, что она делает, является антипаттерном. Я не имею в виду, что использование %- это антипаттерн; очевидно, что это не так. На самом деле, если предположить, что многие из читателей этого поста будут новичками, обучение их такому стилю написания комментариев окажет негативное влияние на их развитие как программистов.
Давуд ибн Карим,

4

Вы в основном сами объяснили ответ, но вот код на всякий случай.

if((x % 10) == 0) {
  // Do this
}
if((x > 10 && x < 21) || (x > 30 && x < 41) || (x > 50 && x < 61) || (x > 70 && x < 81) || (x > 90 && x < 101)) {
  // Do this
}

2
Исправьте x < 41 x > 50и поставьте скобки.
101010

1
@ 40two, Технически, operator&&имеет более высокий приоритет, чем operator||, так что это нормально, но я почти уверен, что GCC все равно предупреждает об этом.
Крис

18
Рассмотрите возможность представления неравенства 10 < x < 21как 10 < x && x < 21вместо x > 10 && x < 21. Неравенство легче читать, если оно находится в том же порядке, в каком вы бы написали его математически.
Эрик Липперт

5
Этот код довольно нечитабелен и мало говорит о реальной логике. Мне не нравится этот ответ.
Дариуш

3
Я голосую против этого, потому что вы точно ответили, что сделал OP.
Бруно Феррейра

3

Возможно, вы слишком много думаете об этом.

if (x % 10)
{
   .. code for 1..9 ..
} else
{
   .. code for 0, 10, 20 etc.
}

Первая строка if (x % 10)работает, потому что (a) значение, кратное 10, вычисляется как '0', другие числа дают их остаток, (b) значение 0 в an ifсчитается false, любое другое значение считаетсяtrue .

Редактировать:

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

if (((x-1)/10) & 1)
{
  .. code for 10, 30, ..
} else
{
   .. code for 20, 40, etc.
}

x/10возвращает любое число от 0 до 9 как 0, от 10 до 19 как 1и так далее. Проверка на четность или нечетность - & 1говорит вам, четное это или нечетное. Так как ваши диапазоны на самом деле равны «от 11 до 20», вычтите 1 перед тестированием.


1

Призыв к удобочитаемости

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

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

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

#include <stdio.h>

enum {NO, YES, WINNER};
enum {OUT_OF_RANGE=-1, ODD, EVEN};

int notInRange(int square) {
  return(square < 1 || square > 100)?YES:NO;
}

int isEndOfRow(int square) {
  if (notInRange(square)) return OUT_OF_RANGE;
  if (square == 100) return WINNER; // I am making this up...
  return (square % 10 == 0)? YES:NO;
}

int rowType(unsigned int square) {
  // return 1 if square is in odd row (going to the right)
  // and 0 if square is in even row (going to the left)
  if (notInRange(square)) return OUT_OF_RANGE; // trap this error
  int rowNum = (square - 1) / 10;
  return (rowNum % 2 == 0) ? ODD:EVEN; // return 0 (ODD) for 1-10, 21-30 etc.
                                       // and 1 (EVEN) for 11-20, 31-40, ...
}

int main(void) {
  int a = 12;
  int rt;
  rt = rowType(a); // this replaces your obscure if statement

  // and here is how you handle the possible return values:
  switch(rt) {
  case ODD:
    printf("It is an odd row\n");
    break;
  case EVEN:
    printf("It is an even row\n");
    break;
  case OUT_OF_RANGE:
    printf("It is out of range\n");
    break;
  default:
    printf("Unexpected return value from rowType!\n");
  }

  if(isEndOfRow(10)==YES) printf("10 is at the end of a row\n");
  if(isEndOfRow(100)==WINNER) printf("We have a winner!\n");
}

3
Разве он не пытается зайти слишком далеко с помощью YESи NO?
рмобис

@Raphael_ - вполне может быть: я просто показывал «например». Очевидно, что многие люди используют истину / ложь. Но я никогда не могу вспомнить (потому что разные языки используют разные соглашения): это есть TRUE, Trueилиtrue ? И какие файлы заголовков мне нужно было бы включить в обычный C? Так что я свернул свой. Интересно, это то, что вызвало отрицательный голос ...
Флорис

1

Для первого:

if (x % 10 == 0)

будет применяться к:

10, 20, 30, .. 100 .. 1000 ...

Для второго:

if (((x-1) / 10) % 2 == 1)

будет подавать заявку на:

11-20, 31-40, 51-60, ..

В основном мы сначала делаем, x-1чтобы получить:

10-19, 30-39, 50-59, ..

Затем делим их на, 10чтобы получить:

1, 3, 5, ..

Поэтому мы проверяем, не является ли этот результат нечетным.


1

Вы можете попробовать следующее:

// Multiple of 10
if ((num % 10) == 0)
{
   // Do something
}
else if (((num / 10) % 2) != 0)
{
    // 11-20, 31-40, 51-60, 71-80, 91-100
}
 else
{
    // Other case
}

В вопросе OP проверка на кратность 10 не связана с проверкой диапазона, а проверка диапазона 20 должна быть в том же диапазоне 11, с вашим кодом ((20/10)% 2) -> ( 2% 2) -> 0
Serpiton

0

Я знаю, что на этот вопрос так много ответов, но я все равно брошу сюда свой ...

Взято из полного кода Стива МакКоннелла. , 2-е издание: «Таблицы доступа по лестнице:

Еще один вид доступа к таблицам - это ступенчатый метод. Этот метод доступа не такой прямой, как структура индекса, но он не тратит так много места для данных. Общая идея ступенчатых структур, показанная на рис. 18-5, состоит в том, что записи в таблице действительны для диапазонов данных, а не для отдельных точек данных.

Введите описание изображения здесь

Рисунок 18-5 Лестнично-ступенчатый подход классифицирует каждую запись, определяя уровень, на котором она попадает в «лестницу». «Шаг» определяет его категорию.

Например, если вы пишете программу выставления оценок, диапазон ввода «B» может составлять от 75 до 90 процентов. Вот несколько классов, которые вам, возможно, придется когда-нибудь запрограммировать:

Введите описание изображения здесь

Чтобы использовать метод ступенчатой ​​лестницы, вы помещаете верхний предел каждого диапазона в таблицу, а затем пишете цикл, чтобы сравнить результат с верхним пределом каждого диапазона. Когда вы находите точку, в которой оценка сначала превышает верхнюю границу диапазона, вы знаете, какова оценка. При использовании техники ступенчатой ​​лестницы вы должны быть осторожны, чтобы правильно обрабатывать конечные точки диапазонов. Вот код в Visual Basic, который выставляет оценки группе студентов на основе этого примера:

Введите описание изображения здесь

Хотя это простой пример, вы можете легко обобщить его для работы с несколькими студентами, несколькими схемами выставления оценок (например, разными оценками для разных уровней баллов по разным заданиям) и изменениями в схеме выставления оценок ».

Код завершен , 2-е издание, страницы 426–428 (Глава 18).


почему вы думаете, что это неправильный вопрос? просто потому, что я не
привел

0

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

Это может помочь сделать вашу программу более гибкой, если позже вы решите, что вам нужна версия игры для малышей на доске 6 x 6 или расширенная версия (в которую вы можете играть всю ночь) на доске 40 x 50. .

Поэтому я бы закодировал это следующим образом:

// What is the size of the game board?
#define ROWS            10
#define COLUMNS         10

// The numbers of the squares go from 1 (bottom-left) to (ROWS * COLUMNS)
// (top-left if ROWS is even, or top-right if ROWS is odd)
#define firstSquare     1
#define lastSquare      (ROWS * COLUMNS)
// We haven't started until we roll the die and move onto the first square,
// so there is an imaginary 'square zero'
#define notStarted(num) (num == 0)
// and we only win when we land exactly on the last square
#define finished(num)   (num == lastSquare)
#define overShot(num)   (num > lastSquare)

// We will number our rows from 1 to ROWS, and our columns from 1 to COLUMNS
// (apologies to C fanatics who believe the world should be zero-based, which would
//  have simplified these expressions)
#define getRow(num)   (((num - 1) / COLUMNS) + 1)
#define getCol(num)   (((num - 1) % COLUMNS) + 1)

// What direction are we moving in?
// On rows 1, 3, 5, etc. we go from left to right
#define isLeftToRightRow(num)    ((getRow(num) % 2) == 1)
// On rows 2, 4, 6, etc. we go from right to left
#define isRightToLeftRow(num)    ((getRow(num) % 2) == 0)

// Are we on the last square in the row?
#define isLastInRow(num)    (getCol(num) == COLUMNS)

// And finally we can get onto the code

if (notStarted(mySquare))
{
  // Some code for when we haven't got our piece on the board yet
}
else
{
  if (isLastInRow(mySquare))
  {
    // Some code for when we're on the last square in a row
  }


  if (isRightToLeftRow(mySquare))
  {
    // Some code for when we're travelling from right to left
  }
  else
  {
    // Some code for when we're travelling from left to right
  }
}

Да, это многословно, но ясно, что происходит на игровом поле.

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

Я бы также позволил изменить ориентацию экрана в любое время, в середине игры - все, что вам нужно сделать, это переключить значения ROWS и COLUMNS, оставив все остальное (текущий номер квадрата, на котором находится каждый игрок, и начальные / конечные квадраты всех змей и лестниц) без изменений. Затем вам «просто» нужно красиво нарисовать доску и написать код для своей анимации (я предполагаю, что это было целью ваших ifутверждений) ...


Я также поддержал ответ Флориса - другой стиль достижения аналогичного результата, которого я не видел до того, как написал свой ответ
Лоуренс Реншоу

2
вы должны использовать встроенную функцию вместо#define
Bryan Chen

При использовании #defineинструкций, похожих на функции, рекомендуется заключать аргументы в скобки там, где они появляются в раскрытии. Так что вместо #define finished(num) (num == lastSquare)тебя надо писать #define finished(num) ((num) == lastSquare). Причина в том, что если вы используете такую ​​инструкцию с выражением, содержащим оператор с достаточно низким приоритетом, вы не получите ожидаемого ответа. В этом случае, если вы не используете лишние круглые скобки, то finished(a & b)раскрывается, (a & b == lastSquare)что почти наверняка не то, что вам нужно.
Давуд ибн Карим
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.