问题:

素数

在世博园某信息通信馆中,游客可利用手机等终端参与互动小游戏,与虚拟人物Kr. Kong 进行猜数比赛。

当屏幕出现一个整数X时,若你能比Kr. Kong更快的发出最接近它的素数答案,你将会获得一个意想不到的礼物。

例如:当屏幕出现22时,你的回答应是23;当屏幕出现8时,你的回答应是7;

若X本身是素数,则回答X;若最接近X的素数有两个时,则回答大于它的素数。

输入:第一行:N 要竞猜的整数个数

接下来有N行,每行有一个正整数X

输出:输出有N行,每行是对应X的最接近它的素数

样例:输入

4

22

5

18

8

输出

23

5

19

7

原代码:

 #include <stdio.h>
int prime(int x) //判断是否为素数
{
int i; if (x==)
return ; for(i=;i<x;i++)
{
if(!(x%i))
return ;
} return ;
} int main()
{
int i,j,n,a[],temp1,temp2;
int prime(int x); //printf("n=");
scanf("%d",&n); for (i=;i<n;i++) //输入相应数值
{
scanf("%d",&a[i]);
} for(i=;i<n;i++)
{ temp1=(temp2=a[i]);
for(j=a[i];;j++) //求出大于等于数值的素数
{
if(prime(j))
{
temp1=j;
break;
}
} for(j=a[i]-;;j--) //求出小于数值的素数
{
if(prime(j))
{
temp2=j;
break;
}
} printf("%d\n",temp1-a[i]<=a[i]-temp2?temp1:temp2);
} return ;
}

评析:

    int i,j,n,a[],temp1,temp2;
int prime(int x);

  老问题,变量太多,数据结构设计不合理,函数类型定义位置不当。实际上这种问题有一个以不变应万变的“标准”句型:

#include <stdio.h>

int main( void )
{ int n ; scanf("%d" , &n); while ( n -- > )
{
//定义解决问题需要的变量
//输入测试数据
//解决问题
} return ;
}

  其中,裸体的“scanf("%d" , &n);”只在这种特定情况(刷题)下可以接受,对于一般情况,在输入前给用户一个提示信息为好。

    for (i=;i<n;i++)    //输入相应数值
{
scanf("%d",&a[i]);
} for(i=;i<n;i++)
{ temp1=(temp2=a[i]);
for(j=a[i];;j++) //求出大于等于数值的素数
{
if(prime(j))
{
temp1=j;
break;
}
} for(j=a[i]-;;j--) //求出小于数值的素数
{
if(prime(j))
{
temp2=j;
break;
}
} printf("%d\n",temp1-a[i]<=a[i]-temp2?temp1:temp2);
}

  显然应该合并为一个for语句。原作者大概误以为必须全部输入之后才能开始解决问题,估计是题目对输入输出样式的说明容易让人误解,实际上输入与输出是相互独立的。不必拘泥于先全部输入后再开始逐个解决问题。

        for(j=a[i];;j++)        //求出大于等于数值的素数
{
if(prime(j))
{
temp1=j;
break;
}
} for(j=a[i]-;;j--) //求出小于数值的素数
{
if(prime(j))
{
temp2=j;
break;
}
}

  这里存在几方面的问题,下面逐个评述。

  首先,抛开具体构思,仅仅从代码形式上来说这中写法就是不能容忍的。

  代码写得一定要“拽”(DRY)(参见代码写得要"拽"(DRY)——《C解毒》试读)。

  而这里的两条for语句,本质上是一模一样的。可以说,一个是对另一个的复制粘贴(如果写的时候连复制粘贴都没用到,而是老老实实一个字一个写成的,那就更成问题了。那说明连计算机都不会用)。

  所以无论如何这两条语句都应该想到用函数实现:

temp1 = find_prime(a[i],);   //求从a[i]开始的第一个素数(步长为1);
temp2 = find_prime(a[i]-,-);//求从a[i]-1开始的第一个素数(步长为-1);

  这样写代码要漂亮多了。

  再说一下逻辑上的缺陷。

  “大于等于”的素数求出之后,如果是“等于”,那么问题已经得解,就没必要执行第二条循环语句了,所以应该跳过第二条循环语句。

    for( i =  ; i < n ; printf("%d\n",solve) , i++ )
{
for(j=a[i];;j++) //求出大于等于数值的素数
{
//……
}        if ( temp1 == a[i] )
       {
        //输出该素数(temp1或a[i])
        continue ; //转到计算下一个a[i]
       } for(j=a[i]-;;j--) //求出小于数值的素数
{
//……
}
       printf("%d\n",temp1-a[i]<=a[i]-temp2?temp1:temp2);
}

  如果用函数求下一个素数,代码还可以写得更漂亮一些:

    for(i=;i<n;i++)
{
int temp1 , temp2 ; if ( (temp1 = find_prime(a[i],)) == a[i] )
{
printf("%d\n",temp1);//输出temp1
        continue ;
        }         temp2 = find_prime(a[i]-,-) printf("%d\n",temp1-a[i]<=a[i]-temp2?temp1:temp2);
}

  这显然要比原来的代码干净整洁多了。如果觉得两条printf()调用不好,还可以这样写(又是逗号表达式,呵呵):

    for( i =  ; i < n ; printf("%d\n",solve) , i++ )
{
int temp1 , temp2 , solve ; if ( (temp1 = find_prime(a[i],)) == a[i] )
{
solve = temp1 ;
           continue ;
        }         temp2 = find_prime(a[i]-,-)
        solve = temp1-a[i]<=a[i]-temp2?temp1:temp2 ;
}

  此外要说一下的是,无论是在main()中还是在prime()函数中,作者都没有认真考虑当输入整数并不处于两个素数之间的情况。事实上这是有可能的。比如输入为1,那么找小于1的素数是不可能的。但在原代码中,明显能看出作者根本就没意识到这件事(main()中没有相关的处理,prime()函数没有对0和负整数进行判断)。这应该说是原代码中的一个BUG。

  最后再说一下总体思路方面的问题。

  作者的思路是先“求出大于等于数值的素数”,再“求出小于数值的素数”。这个思路可行,但不够好。比较好的思考应该是:

  1. 看这个数(假定是X)是不是素数
  2. 如不是,判断X+1是不是素数
  3. 如不是,判断X-1是不是素数
  4. 如不是,判断X+2是不是素数
  5. 如不是,判断X-2是不是素数
  6. ……

  这种想法更自然,一旦找到素数就完活儿,而不会做无用功。

  而原作者的代码则存在这样的可能:先求出一个比X大很多的素数,但其实解是X-1;或者求出大于X的素数是X+1,又去找出一个比X小很多的素数。这两种情况下,计算机总要做一些很无聊且无意义的工作。

  当然,原代码还可以继续改进,但在错误的总体思路指导下,改进的空间极其狭仄。即使改了,也无法彻底根除指令雍余的问题。

  而按照下面次序寻找最近素数

  X X+1 X-1 X+2 X-2 X+3……

  则不存在类似问题。

  不难发现,这个序列相邻两项差的绝对值,恰好构成自然数列:1 2 3 4 5 ……

  据此,重构如下:

重构:

/*
问题:
素数
在世博园某信息通信馆中,游客可利用手机等终端参与互动小游戏,与虚拟人物Kr. Kong 进行猜数比赛。
当屏幕出现一个整数X时,若你能比Kr. Kong更快的发出最接近它的素数答案,你将会获得一个意想不到的礼物。 例如:当屏幕出现22时,你的回答应是23;当屏幕出现8时,你的回答应是7;
若X本身是素数,则回答X;若最接近X的素数有两个时,则回答大于它的素数。 输入:第一行:N 要竞猜的整数个数
接下来有N行,每行有一个正整数X
输出:输出有N行,每行是对应X的最接近它的素数 样例:输入
4
22
5
18
8
输出
23
5
19
7 作者:薛非
出处:http://www.cnblogs.com/pmer/ “C语言初学者代码中的常见错误与瑕疵”系列博文
*/ #include <stdio.h>
#include <stdbool.h> int get_nearest( int);
bool be_prime( int ); int main( void )
{ unsigned n ; puts( "数据组数=?" );
scanf("%u" , &n); //这里没写输入提示 while ( n -- > 0u )
{
int x ; scanf("%d" , & x);
printf("%d\n" , get_nearest( x ) );
} return ;
} int get_nearest( int x )
{
int n , s ;//步长增量,符号 for ( n = , s = - ; ! be_prime( x ) ; n ++ , s = -s , x += s * n )
{
} return x ;
} bool be_prime( int x )
{
int fac ; if ( x <= )
return false ; for ( fac = ; fac * fac <= x ; fac ++ )
if ( x % fac == )
return false ; return true ;
}

不足:

  重构的代码中,be_prime()函数,仅仅从功能角度来说并没有什么问题。但若放在问题的背景下,它的效率太低了。这个函数需要反复地对每一个x都进行for ( fac = 2u ; fac * fac <= x ; fac ++ )这样的循环判断,如果问题要求判断的只有一个整数,这种写法也许无可厚非。但问题要求判断的是X X+1 X-1 X+2 X-2 X+3……这样一个序列,这样的写法就非常笨拙了。但是若想进一步改进却也不那么容易。这次就不改了,我将在后续的博文中给出改进的写法。

C语言初学者代码中的常见错误与瑕疵(5)的更多相关文章

  1. C语言初学者代码中的常见错误与瑕疵(23)

    见:C语言初学者代码中的常见错误与瑕疵(23)

  2. 一个超复杂的间接递归——C语言初学者代码中的常见错误与瑕疵(6)

    问题: 问题出处见 C语言初学者代码中的常见错误与瑕疵(5) . 在该文的最后,曾提到完成的代码还有进一步改进的余地.本文完成了这个改进.所以本文讨论的并不是初学者代码中的常见错误与瑕疵,而是对我自己 ...

  3. C语言初学者代码中的常见错误与瑕疵(19)

    见:C语言初学者代码中的常见错误与瑕疵(19)

  4. C语言初学者代码中的常见错误与瑕疵(14)

    见:C语言初学者代码中的常见错误与瑕疵(14) 相关链接:http://www.anycodex.com/blog/?p=87

  5. 分数的加减法——C语言初学者代码中的常见错误与瑕疵(12)

    前文链接:分数的加减法——C语言初学者代码中的常见错误与瑕疵(11) 重构 题目的修正 我抛弃了原题中“其中a, b, c, d是一个0-9的整数”这样的前提条件,因为这种限制毫无必要.只假设a, b ...

  6. C语言初学者代码中的常见错误与瑕疵(9)

    题目 字母的个数 现在给你一个由小写字母组成字符串,要你找出字符串中出现次数最多的字母,如果出现次数最多字母有多个那么输出最小的那个. 输入:第一行输入一个正整数T(0<T<25) 随后T ...

  7. 要心中有“数”——C语言初学者代码中的常见错误与瑕疵(8)

    在 C语言初学者代码中的常见错误与瑕疵(7) 中,我给出的重构代码中存在BUG.这个BUG是在飞鸟_Asuka网友指出“是不是时间复杂度比较大”,并说他“第一眼看到我就想把它当成一个数学问题来做”之后 ...

  8. C语言初学者代码中的常见错误与瑕疵(7)

    问题: 矩形的个数 在一个3*2的矩形中,可以找到6个1*1的矩形,4个2*1的矩形3个1*2的矩形,2个2*2的矩形,2个3*1的矩形和1个3*2的矩形,总共18个矩形.给出A,B,计算可以从中找到 ...

  9. C语言初学者代码中的常见错误与瑕疵(1)

    曾在豆瓣上看到过一个小朋友贴出他自己的代码(http://www.douban.com/group/topic/40293109/),当时随口指点了几句.难得这位小朋友虚心修正.从善如流,不断地改,又 ...

随机推荐

  1. php的SAPI,CLI SAPI,CGI SAPI

    首先一个问题:在命令行下执行:php -r 'echo 12;' 控制台会打印出 12: 这个过程不是很奇妙么,我输入的是shell命令,但是执行的却是php脚本.php脚本执行完成之后的输出还能在控 ...

  2. 基于HTML5技术的电力3D监控应用(三)

    继(一)和(二)之后不少,不少网友问我移动终端的使用问题,因为我们项目这次采用Android平板终端,所以我对这方面有点肤浅的研究,这篇分享些项目经验总结,希望对大家有所帮助. 电力3D项目去年底刚立 ...

  3. CMD魔法堂:支持显示UTF8编码的中文

    一.前言 在Unbuntu中用sqlite3-command-line操作sqlite3还好好的,到了windows下查询表内容时发现中文全部乱码了!马上想到sqlite3内部使用utf-8对字符进行 ...

  4. ADO.NET基础01

    数据库中数据的导入导出   在使用一些数据库时,很多时候都要将文件导入导出到指定的文件夹中: 数据的导入导出就必须用到stream函数,这就必须用到Using System.IO的命名空间: **在数 ...

  5. SQL Server 2008 评估期已过解决方法

    SQL Server 2008有180天的试用期,过期后会提示“评估期已过”的提示. 1.进入SQL Server安装中心: 2.选择“维护”-“版本升级” 3.输入密钥: 其他的根据提示操作. 附S ...

  6. C#初入串口通信(串行通信)总结

    使用WinFrom来实现: 首先要知道串口通信协议以及原理 原理大概提一下:要自己翻阅看.(http://book.51cto.com/art/200911/162532.htm或者http://hi ...

  7. PHP OAuth2 Server库

    想找比较正宗的库,查了蛮久的.最后在 oauth官方站上,看到PHP版本的相关链接. 发现都是php 5.3版本以上的环境,基于命名空间的写法编写的. 访问下面这个页面,难得,发现文档给出了5.2版本 ...

  8. PHPWind 8.7中代码结构与程序执行顺序

    pw9在此不谈,他是完全重构的作品,是完全MVC下的体系.当然,其中很多东西在PW8.7下已经可见端倪. 主要代码结构 1. 以现代的观点,PW是多入口应用模式,程序根目录下的文件几乎都是入口: 2. ...

  9. js 自带的 reduce() 方法

    1.方法说明 , Array的reduce()把一个函数作用在这个Array的[x1, x2, x3...]上,这个函数必须接收两个参数,reduce()把结果继续和序列的下一个元素做累积计算,其效果 ...

  10. 初学Java9:学习Mybatis时报错:Parameter 'name' not found. Available parameters are [1, 0, param1, param2]

    报错-->Parameter 'name' not found. Available parameters are [1, 0, param1, param2] 百度找到这篇文章完成修改 htt ...