确定数字是十的倍数还是在一组特定范围内


103

我的程序中有一些循环。我可以写出伪代码,但我不确定如何逻辑地编写它们。

我需要 -

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 2014年

39
@ user3419168:编译器并不关心您的代码的可读性;它将在不到一秒钟的时间内完成编译。但是对于阅读代码的人来说,您所做的选择可能会使它在几秒钟,几分钟,几小时内甚至从不被理解。这增加了成本。人们获得阅读和理解代码的报酬,因此使他们变得容易。始终编​​写生产代码以最大程度地提高可读性,并记住简洁并不一定会使代码性能更好。
埃里克·利珀特

2
@AmadeusDrZaius-我很少做同样的事情,但是仅用于性能关键的部分。称为100M次的最紧密循环合格-蛇和梯子游戏中的if语句不合格。您在它们之间划清界限的位置是个人选择。
弗洛里斯2014年

2
我讨厌这样说,但是在完成了足够的公司工作之后,由于初学者编写了真实的代码,所以我不得不建议强行使用它。因为那样的话,新手就会理解它并且不会破坏它。悲伤但真实-在某些情况下,它很聪明。
理查德·勒·

22
这是一个体面的问题,我根本不想从海报上夺走任何东西,但是这不值得500+分。这就是我们最终对那些拥有成千上万个要点的人胡言乱语的结果。(如果有其他评论,请随意提出。)
GaTechThomas 2014年

Answers:


86

对于第一个,检查数字是否为使用倍数:

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)-如果看到的话我会哭。
Daniel Kamil Kozar 2014年

32
@DanielKamilKozar,如您所愿。
Winston Ewert 2014年

2
@ user3419168,它本身就让人想知道它到底意味着什么。它没有暗示它在试图做什么。这就是为什么我在编辑中显示了一个版本,该版本将逻辑拆分为一个函数,从而使计算更清楚实际在做什么。
Winston Ewert 2014年

3
最好也断言num >= 11为(1)规定了下限,并且(2)%负数也返回负数。(我必须承认& 1这里使用的是“更安全”的方法,但也要假设其他知识。)
usr2564301 2014年

2
对于+1,它进入范围列表的原因,并以可读的方式显示。海事组织,第一步是将getRow(num) % 2 == 0功能也包装起来,使目的明确。bool inEvenRow(int num){ return getRow(num) % 2 ==0;}
Mindor先生2014年

40

if(num是10的倍数){执行此操作}

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

如果(数字在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得到第一个数字,并且可以通过除以2来检查是否为1的余数来检验它是否为奇数。

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

考虑到较长但可维护的代码与较短的“聪明”代码之间的权衡,我每次都会选择更长和更清晰的代码。至少,如果您想变得聪明一点,请附上一条评论,以准确说明您要完成的工作。

这有助于假定下一个要使用该代码的开发人员已经武装并且知道您的住所。:-)


7
我仍然会寻找聪明的代码,但是通过提取函数将其变成可维护的代码。如果最后一点说的话,它将是同样可读的&& isTensDigitOdd(num),也许在函数定义之前加上注释来解释它的作用。如果存在这样的模式,那么说明该模式的原因的注释对于可维护性imo会有所启发。
克里斯

3
克里斯,当“聪明”有明显的优势时,这是一个不错的策略:代码短得多(这意味着打字错误的机会更少,尤其是当它改变时),或者效率大大提高了。简洁,清晰和效率之间总是存在一个折衷,找到一个好的折衷方案是一项很好的开发技能。(参见stackoverflow.com/a/2151844/29157用于窃笑。)
亚当利斯

1
这是一个更好的方法。比“智能代码”更容易理解,性能差异可以忽略不计。
user1477388 2014年

@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 2014年

8
@MSharathHegde,因为它需要GCC扩展,这不是标准的一部分,并且某些编译器不支持它
Bryan Chen

5
这是正确的答案,因为可以立即看出目的是什么。所有带有模数的“聪明”答案都是维护噩梦,即使有评论也是如此。
smirkingman

@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;
    });
}

为简单起见,我使用了多态lambda(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);

有一个活生生的例子在这里


干净的解决方案。我可能会使用单个数组,因为您可以将其格式化为每行2个数字来表示对。
凯文·林

@ HunterGuy2,很好。实际上,我将其更改为可成对运行,因为出于某种原因,我只是在考虑zip迭代器。
克里斯

真的很棒的STL方法!爱它!
higuaro 2014年

5

第一个很简单。您只需要对num值应用模运算符:

if ( ( num % 10 ) == 0)

由于C ++会将每个非0的数字都视为true,因此您还可以编写:

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 2014年

2
请永远不要这样做。通过减慢阅读器的速度并强迫他们两次阅读所有内容,实际上降低了可读性。任何不理解这if (num % 10 == 0)意味着// Check if it's a multiple of 10不应该维护您的代码的程序员。这是众所周知的反模式。
戴伍德·伊本·卡里姆

1
@DavidWallace参见上面的评论。我们不能保证本文的读者会了解这种反模式。
La-comadreja 2014年

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并放入括号。
2014年

1
@ 40two,从技术上讲,operator&&它的优先级比更高operator||,因此可以,但是我可以肯定,GCC还是会警告它。
克里斯

18
考虑将不等式表示10 < x < 2110 < x && x < 21而不是x > 10 && x < 21。当不等式的顺序与数学上写的顺序相同时,更容易阅读。
埃里克·利珀特

5
这段代码是相当不可读的,几乎没有涉及实际逻辑。我不喜欢这个答案。
Dariusz 2014年

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值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
是不是想用得太远推YESNO
rmobis

@Raphael_-可能是:我只是显示“例如”。显然,许多人使用对/错。但我永远记得(因为不同的语言使用不同的约定):是的TRUETruetrue?我需要在普通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 2014年

0

我知道这个问题有很多答案,但是无论如何我都会把我扔在这里...

摘自Steve McConnell的Code Complete,第二版:“阶梯式访问表:

另一种表访问方式是阶梯方法。这种访问方法不像索引结构那样直接,但是它不会浪费太多的数据空间。如图18-5所示,阶梯结构的总体思想是,表中的条目对数据范围有效,而对不同的数据点有效。

在此处输入图片说明

图18-5阶梯方法通过确定每个条目击中“楼梯”的级别来对每个条目进行分类。它命中的“步骤”确定其类别。

例如,如果您正在编写评分程序,则“ B”输入范围可能从75%到90%。您可能有一天需要编程编制以下一系列成绩:

在此处输入图片说明

要使用阶梯方法,您需要将每个范围的上端放入表格中,然后编写一个循环以对照每个范围的上端检查分数。当您找到分数首次超过范围最高点的点时,您便知道该分数是多少。使用阶梯技术时,您必须注意正确处理范围的端点。这是Visual Basic中的代码,该代码根据此示例为一组学生分配成绩:

在此处输入图片说明

尽管这是一个简单的示例,但是您可以轻松地将其概括为处理多个学生,多个评分方案(例如,针对不同作业的不同分数级别的不同成绩)以及评分方案的更改。”

Code Complete,第二版,第426-428页(第18章)。


您为什么认为这是错误的问题?仅仅因为我没有在OP案例中提供示例并不意味着我回答了错误的问题……
lauCosma 2014年

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)其扩展到您想要的范围之外。
达伍德·伊本·卡里姆
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.