*
,/
和%
。太复杂了吗?有没有更好的方法可以执行相同的操作?我还提到了geeksforgeeks中给出的逻辑。#1 楼
我看到了一些您可能想使用的改进代码的方法。x
。消除2的倍数
由于9和2没有公因子,因此可以通过将传入的
0
右移直到最低有效位为非零。消除未使用的变量
通过对代码进行较小的重组,您可以消除大多数变量并使代码更短,更快。并且更容易阅读。
考虑实施测试程序
您显然已经进行了一些测试,但是发布带有要检查的代码的测试可能会帮助其他人检查代码正确地。
将它们放在一起
这是我使用所有这些建议得出的结果:
#include <stdio.h>
#include <assert.h>
int isDivby9(int x)
{
while (0 == (x & 1)) {
x >>= 1;
}
if(x<9)
return 0;
int divby8 = x >> 3;
while(divby8 >= 9) {
divby8 -= 9;
}
return divby8 == (x & 7);
}
int main()
{
for (int i=1; i < 1000000; ++i)
assert(isDivby9(i) == (i%9 == 0));
}
结果
在我的机器(64位Linux机器)上,原始代码在2.3秒内运行,而上述版本在1.5秒内完成;具有相同数学结果的性能有了显着提高。相比之下,@ Edenia答案中的直接方法在同一台计算机上花费18.8秒。
所有程序均使用带有-O2优化的gcc 4.9.2进行了编译。知道有一个更好的算法,但没想到。我终于在StackOverflow上遇到了一个类似问题的绝佳答案。有了这个出色的答案,我将有限状态机的实现翻译成C语言,并提出了以下解决方案:
#include <limits.h>
struct state {
int nextstate[2];
};
int isDivby9 (int num)
{
static const int HIGH_BIT = INT_MAX - (INT_MAX >> 1);
static const struct state states[9] = {
{{0, 1}}, {{2, 3}}, {{4, 5}},
{{6, 7}}, {{8, 0}}, {{1, 2}},
{{3, 4}}, {{5, 6}}, {{7, 8}}
};
if (num < 0)
num = -num;
int s = 0;
for ( ; num ; num <<= 1) {
s = states[s].nextstate[(num & HIGH_BIT) ? 1 : 0];
}
return s==0;
}
从MSB开始的每一位都将状态机驱动到下一个状态。状态保存在变量x
中,并且s
和0
位的分支是两个1
条目。它运作良好(包括负数和零),并且速度非常快。实际上,在我的机器上,此例程需要0.045秒。 更新结果
在我的机器上进行更简洁的定时测试,并调整所有例程以使其在负数上也能正常工作,这是我在这台机器上发现的内容: >
861092 modulus operator
1152840 JS1
1581770 gnasher729
2479987 Simon
8961866 Edward DFA function
所以
nextstate
运算符是最快的,其次是@ JS1的例程,接着是@ gnasher729的例程,接着是@Simon的,接着是DFA例程(相当大的距离!)自然地,这在具有不同体系结构的不同机器上可能会有所不同,因此一如既往,建议您在自己的实际硬件上使用定时例程。
我学到了一些可能对下次我要合成自己的逻辑或在没有乘法指令的嵌入式微处理器上工作。
评论
\ $ \ begingroup \ $
为什么只测试正数?
\ $ \ endgroup \ $
–埃里克·利珀特
15年3月27日在16:54
\ $ \ begingroup \ $
@EricLippert:仅仅因为这是原始函数在其上工作的领域,所以如果以前它“能正常工作”,我就推断出它是感兴趣的领域。
\ $ \ endgroup \ $
–爱德华
15年3月27日在17:36
\ $ \ begingroup \ $
虽然我明白你的意思,但这是一个代码审查站点;我认为编写代码的人实际上并未阅读或理解他们的任务,并且他们正在从互联网上复制随机代码,以期希望它能起作用。 :-)
\ $ \ endgroup \ $
–埃里克·利珀特
15年3月27日在18:25
\ $ \ begingroup \ $
@EricLippert:我想我对人类的行为比较乐观,所以希望您的评论(以及我提供的测试代码)能促使人们对“ 0被9整除吗? ”和“ -81是否可被9整除?”
\ $ \ endgroup \ $
–爱德华
15年3月27日在18:34
\ $ \ begingroup \ $
除非数字小于9的可能性很小,否则似乎每次测试都不是一次优化。
\ $ \ endgroup \ $
–mattdm
15年3月27日在20:33
#2 楼
阅读gnasher729和Simon的答案后,我受到启发,找到了最快的方法来做到这一点。仅使用8分频技巧。在那之后,它进入了这个循环: while(divby8 >= 9)
{
divby8 = divby8 - 9;
}
鉴于大量循环,该循环可以迭代数百万次迭代。
Gnasher729和Simon演示了使用小循环将原始数字分别减少6位和3位的解决方案。基于他们的工作,我提出了以下优化解决方案:
int div9(int x)
{
x = (x >> 15) - (x & 0x7fff);
x = (x >> 9) - (x & 0x1ff);
x = (x >> 6) + (x & 0x3f);
x = (x >> 3) - (x & 0x7);
return x == 0;
}
该函数适用于32位正整数。对于64位正整数,可以在开始处添加以下行:
x = (x >> 30) + (x & 0x3fffffff);
如果允许负整数,则可以在开始处添加此行:
if (x < 0) x = -x;
只是为了好玩
原始问题不允许乘法或取模。但是,只是为了好玩,让我们看看什么是最快的方法。例如,简单地使用模运算有多快?
此解决方案基于此stackoverflow答案。将6移位是为了使原始数字小到足以使用乘法技巧。我的计时程序的功能。它测试从
0
到0x7ffffff4
的所有值。我选择这样做是为了避免在测试循环中使用i%9
,因为与被测试的实际功能相比,i%9
可能要花费大量时间。时间结果
Author Time (seconds)
------ --------------
JS1 (multiply) 2.23
JS1 (modulo) 3.35
JS1 (shifts) 5.70
Gnasher729 16.90
Simon (opt) 30.40
Simon 53.40
西蒙(opt)是他的函数,只有3个循环移位。如您所见,无需循环即可检查2的次方,因此速度更快。
评论
\ $ \ begingroup \ $
做得好!我在自己的答案中添加了计时结果,该结果可以验证您的相对结果并比较其他一些结果。
\ $ \ endgroup \ $
–爱德华
15年3月28日在18:23
#3 楼
我没有检查您的代码是否有效,因为您这样说,所以我认为它可以。您的代码缺乏一致性
if(divby8 == (orgx - (olddivby8 << 3)))
{
//...
}
vs
else{
//...
}
和
if(x>=9)
与while(divby8 >= 9)
我建议使用自动格式化工具。
避免嵌套
通过反转外部
if
语句并返回0
可以避免嵌套。您的嵌套没有那么深,所以它不是主要问题。 不需要的代码
我看不出这些行有什么意义。
else{
status = 0;
}
x = divby8;
评论
您应该添加解释原因的评论。
例如:在
if(x>=9)
上您可以添加// It cannot be divisible by 9 if it is less than 9
评论
\ $ \ begingroup \ $
有什么方法可以避免在代码中进行while循环?
\ $ \ endgroup \ $
– kapil.thakar
2015年3月27日14:34
\ $ \ begingroup \ $
你是对的。如您在这里提到的,我应该删除不需要的代码。
\ $ \ endgroup \ $
– kapil.thakar
2015年3月27日14:35
\ $ \ begingroup \ $
@ kapil.thakar,不确定如何执行此操作。好的,您应该这样做,但是不要编辑问题。
\ $ \ endgroup \ $
–bhathiya-perera
15年3月27日在14:36
\ $ \ begingroup \ $
当然,谢谢您的宝贵建议。我想对你的答案投赞成票,但我没有15分,所以我不能这样做。
\ $ \ endgroup \ $
– kapil.thakar
15年3月27日在14:40
\ $ \ begingroup \ $
@ kapil.thakar好,谢谢,这才是最重要的。享受有趣的编码:)当您有20个代表时,您也可以参加聊天室。
\ $ \ endgroup \ $
–bhathiya-perera
2015年3月27日14:41
#4 楼
如果输入很大,则代码将运行非常长的时间。如果将类型从int更改为int64_t,它可能永远运行。您可以使用以下事实:64%9 == 1,因此(64x + y)%9 ==(x + y)%9。
while (x >= 64) x = (x >> 6) + (x & 0x3f);
while (x >= 9) x -= 9;
即使是最大的x,您总共也要进行不到20次循环迭代。
评论
\ $ \ begingroup \ $
到目前为止,这是针对所述问题的最佳解决方案。我根据您的答案来创建自己的答案。
\ $ \ endgroup \ $
– JS1
15年3月28日在10:57
\ $ \ begingroup \ $
好答案!而且,由于64%9 == 1,我们知道2 ^(6 * n)== 1表示n = 0、1 ... ...因此,如果对4096、262144, 16777216等
\ $ \ endgroup \ $
–爱德华
15年3月28日在18:35
\ $ \ begingroup \ $
一种改进可能是避免循环,但是如果x> = 262144,则运行一些硬编码的迭代;如果x> = 4096,然后如果x> = 64。
\ $ \ endgroup \ $
–超级猫
15年3月28日在22:33
\ $ \ begingroup \ $
在第一个循环之后,保证x小于64 =8²,因此(使用8≡-1模9的事实),您可以用简单的返回(x >> 3)替换最后一个while循环==(x&7)。
\ $ \ endgroup \ $
–伊尔马里·卡洛宁(Ilmari Karonen)
2015年3月29日在12:15
\ $ \ begingroup \ $
@@ IlmariKaronen:好点。如果您这样做,并在顶部添加x =(x >> 18)+(x&0x3ffff),我相信它的运行速度与@ JS1s 32位数字解决方案一样快。
\ $ \ endgroup \ $
–迈克尔·肖(Michael Shaw)
15年3月29日在14:21
#5 楼
int divides9 (int number)
{
int num;
for(num = number; num > 0; num -= 9);
return (num == 0);
}
面对您的限制,我想出的这似乎与您当前的做法有所不同。确实较小。
不仅不必要,而且非常令人困惑:
您使用的名称很长。骆驼的名称很不容易辨认。
是的。
因此可以用
PreferCamelCaseOverSnakeCaseForLongNames
替换prefer_snake_case_over_camel_case_for_long_names
,或者如果您要使用缩写名称-isDivby9
并指定必须检查的数字作为函数参数。 is_divisible_by9
应该做什么?在阅读时,我一无所知。isdivsbl
甚至更糟。那代表所有名字。坚持使用样式,并尝试提出一个简短的名称,该名称应具有合理的意义,并且很容易解释。特别是在声明和orgx
语句之间。使用位运算进行这种算术很少见,而且并不总是那么漂亮。特别是如果不需要它们。正是由于这种情况很少见,除非您是唯一的选择,否则不应该使用它们。它们很难调试。
评论
\ $ \ begingroup \ $
也不是最有效的解决方案...
\ $ \ endgroup \ $
– 5gon12eder
15年3月27日在14:40
\ $ \ begingroup \ $
通过使用加法而不是减法,您会看到性能的提高。只需从0开始num,然后每次迭代增加9。您的条件将是num <= number
\ $ \ endgroup \ $
–埃文·贝克托(Evan Bechtol)
2015年3月27日15:02
\ $ \ begingroup \ $
基本上%很慢。但这可能会在旧C.中引起一些不雅的问题。
\ $ \ endgroup \ $
–爱丁尼亚
2015年3月27日15:08
\ $ \ begingroup \ $
“哪个看起来更容易阅读?” -我认为它们同样容易阅读。同样,isdivsbl是一个可怕的名字建议。
\ $ \ endgroup \ $
– BlueRaja-Danny Pflughoeft
2015年3月27日15:51
\ $ \ begingroup \ $
ThisIsPascalCase,thisIsCamelCase-看到区别了吗?
\ $ \ endgroup \ $
–西蒙·福斯伯格
15年3月28日在14:27
#6 楼
您将此问题标记为“初学者”,但是geeksforgeeks上的算法非常先进。您确定要在简单的重复加法或减法上使用它吗?实际上,当您执行以下操作时,您仍在进行9的重复减法操作: br />我更改了@Edward的代码(那里所有好的建议),以更忠实地实现您链接的算法。性能提升非常明显-在我的计算机上,此时间为0.018s,@ Edward为3.497s,@ Edenia为41.787s。
while(divby8 >= 9)
{
divby8 = divby8 - 9;
}
评论
\ $ \ begingroup \ $
您应该首先查看询问者的代码。
\ $ \ endgroup \ $
– Jamal♦
15年3月27日在20:32
\ $ \ begingroup \ $
和Gnasher的算法一样,我非常喜欢这种算法。我想知道消除2循环的功能是否会更快。该循环使用掩码,比较和移位(总共3个操作)来处理输入的一位。主循环使用比较,移位,减法和掩码(4个操作)来处理输入的3位。因此,我认为您可以摆脱主循环之前的所有内容。
\ $ \ endgroup \ $
– JS1
15年3月28日在8:19
\ $ \ begingroup \ $
@Simon我根据您的回答提出了自己的答案。这样做时,我进行了一个时序测试,结果表明您的主循环本身速度更快,没有2循环因素。
\ $ \ endgroup \ $
– JS1
15年3月28日在10:59
#7 楼
我真的不喜欢@Edenia编写代码的方式。我的就像她的一样,只是我认为它更简洁明了: />我之所以更喜欢我的代码,是因为
for
循环在单个块中完成;尽管这可能是美观的,但对我来说似乎仍然更干净。 我们将数字相加,所以在最后一次运行时,我们将通过增加除数直到达到数字来查看是否落在数字上。
答案将适用于负数,因此请小心在其中插入负数。该代码也是如此;如下所示,并且只检查一次结果,并且对于它的操作仍然很简单。
评论
\ $ \ begingroup \ $
它必须检查每个迭代。您对“更好”有一个有趣的定义。另外我也不知道我是否是唯一认为C#部分不相关的人。
\ $ \ endgroup \ $
–爱丁尼亚
2015年3月27日15:20
\ $ \ begingroup \ $
该代码是可扩展的,它在代码中的任何地方都没有幻数。
\ $ \ endgroup \ $
–马拉奇♦
15年3月27日在15:24
\ $ \ begingroup \ $
“它在代码中的任何地方都没有幻数”。是的,尽管事实如此,但它并没有9。包括函数名称。同样是的,它可以进一步扩展。说..调试检查等。不同级别的复杂度..
\ $ \ endgroup \ $
–爱丁尼亚
2015年3月27日15:26
\ $ \ begingroup \ $
是。现在,当我暗示我的时候...基本方法。它显然可以改进。也已经提到了正算术。我不能说这不是更好。请不要把它当作比赛之类的东西。因为不是。
\ $ \ endgroup \ $
–爱丁尼亚
2015年3月27日15:30
\ $ \ begingroup \ $
while(((i + =除数)<数字);
\ $ \ endgroup \ $
–爱丁尼亚
15年3月27日在15:47
评论
是否可以轻松地在C中对数字进行计数和求和?我不知道C,但是我确实知道,如果数字的总和可以被9整除,那么该数字可以被9整除。考虑到约束,这个小事实很可能是您希望知道的这个问题。然后,使用递归可以使结果函数变得简单。在psuedocode中:如果(numDigits == 1),则返回9 == x;否则,返回isDivBy9(sumOfDigits(x));@WillemRenzema如果数字以10为底的数字之和可以被9整除,则该数字可以被9整除,但是,变量存储在以2为底的数字中。从两个底数转换数字通常需要除法。
我没有迹象表明您已经设计或测试了该程序以使用负数。您的问题是关于数字的,我假设您是指整数,而不是正整数。
这是我在问题和答案@EricLippert中都感到惊讶的部分。特别是考虑到扩展可以确定正数是否可以被9整除的ANY算法多么容易。
相关:检查数字是否可被3整除