三角形输出问题考量

相信很多人在初学某门计算机语言的时候都会做过类似的题目:在控制台上输出用特定字符'拼'出来的某种图形,比如下面的这种三角形:
    *
   ***
  *****
 *******
*********
这样的问题应该算是入门级的了,大多人都是看之,做之,忘之,而今天我就拿这种入门级的题目说事,小问题里也许内含有大道理。

昨晚无意中在编程爱好者论坛看到这样一道三角形输出C语言例题,内容大致如下:
用*号组合成一个三角形!行数由键盘输入(范围为:1~20,输入超过范围,则提示出错)。如:
输入一个数4,则输出以下图形:
     *
    ***
   *****
  *******
共四行;如输入的是6,则输出以下图形:
       *
      ***
     *****
    *******
   *********
  ***********
共六行,依次类推。

对于这类题目,大多数人见到后都会马上敲键盘,也许十分钟或更少时间就能拿出了一个解决方案,比如论坛上发表的一段代码:
int main()
{
        int i, j, n;
        printf("Please enter the number of col(1~20):\n");
        while(1) {
                scanf("%d",&n);
                if(1<=n && n<=20) {
                        break;
                } else {
                        printf("out of range(1~20),please retype:\n");
                }
        }       

        for(i=0; i<n; i++) {
                for(j=i; j<n-1; j++)
                        printf(" ");
                for(j=0; j<2*i+1; j++)
                        printf("*");
                printf("\n");
        }

        return 0;
}
这段代码的实现初步看起来应该是没有问题的,而且很多人的答案与之都大同小异,但是从另外一个角度来看似乎这里存在些小问题。从什么角度呢?从设计角度。我们来看,上面的代码只是从功能的角度去考虑了,导致代码很'死',输出逻辑与图形生成逻辑交叠在一起了,或者说根本谈不上有什么设计的思维在里面。

这时有人会问:这么小的问题还需要设计吗?如果你是在参加ACM竞赛,这么实现一点问题没有,又快又正确。但是如果从一个工程的角度来看,无论问题大小与否,都需要有设计。

类似这种输出三角形或者输出实心(或空心)菱形的问题实际上都可以看成是将一个含有特殊字符的二维数组(或矩阵)输出的一个过程。我们完全可以将输出和形成图形这两种逻辑正交分开。我们的大致思路就是:校验输入 -> 根据输入的参数,确认画布(二维数组)的尺寸 -> 在画布上描点 -> 将画布整体输出到控制台上,这样的逻辑有些类似于Windows GUI图形的输出。

typedef struct {
    int row;
    int col;
    int *p;
} canvas; //画布结构

int prepare_canvas(canvas *p_cns, /* in/out */
                    int row,  /* in */
                    int col); /* in */
void get_option(int *row, /* out */
                int *col); /* out */
void draw(canvas *p_cns); /*in/out */
void show(const canvas *p_cns);
void release_canvas(canvas *p_cns);  /*in/out*/

/* 提供一个宏,方便访问画布上的像素点 */
#define CANVAS_PIXEL(p_cns, i, j) \
     *((p_cns)->p + (i) * (p_cns)->col + (j))

int main(int argc,  char *argv[]) {
    int     rv, row, col;
    canvas   cns;
    
    get_option(&row, &col);
    
    rv = prepare_canvas(&cns, row, col);
    if (rv != 0) {
        printf("fail to prepare_canvas!\n");
        return;
    }
    
    draw(&cns); //描点逻辑
    show(&cns); //输出逻辑

    release_canvas(&cns);
    return 0;
}

void get_option(int *row, int *col) {
    int n;
    do {
        printf("enter the number of lines:\n");
        scanf("%d", &n);
        if (n 20) {
            printf("out of range(3~20), please re-enter.\n");
        } else {
            *row = n;
            *col = 2 * n -1; //确定画布大小
            break;
        }
     } while (1);
}

int prepare_canvas(canvas *p_cns, int row, int col) {

    p_cns->row = row;
    p_cns->col = col;
    p_cns->p = (int*)malloc(row * col * sizeof(int));
    if (p_cns->p == NULL) {
        printf("fail to malloc the canvas!\n");
        return -1;
    }
    
    memset(p_cns->p, ' ', row * col * sizeof(int)); //将画布上的'像素点'都置为空白
    return 0;
}

void draw(canvas *p_cns) { //生成图形逻辑,我们的focus点
        int row, col;
        for (row = 0; row

row; row++) {
                for(col =p_cns->row-1-row ; col row-1+row; col++) {
                         CANVAS_PIXEL(p_cns, row, col) = '*';
                }
        }
}

void show(const canvas *p_cns) { //图形的通用输出逻辑,此后无须关注
    int row, col;
    for (row = 0; row

row; row++) {
                for (col = 0; col

col; col++) {
                        printf("%c", CANVAS_PIXEL(p_cns, row, col));
                }
                printf("\n");
        }
}

void release_canvas(canvas *p_cns) {
    if (p_cns->p != NULL) {
        free(p_cns->p);
    }
}

也许单单从上面的例子来看,你会觉得这样做的代码量会多出几倍。这里我们不妨再考虑另一个道问题:输入一个奇数n,输出对角线长为n的实心菱形。比如:
  *  
 ***
*****
 ***
  *  

使用我们上面的设计,我们只需改造几个地方就可以完成这个问题。main函数是不需要改动的。我们需要关注的只是输入的校验逻辑和draw的逻辑。

void get_option(int *row, int *col) {
    int n;
    do {
        printf("enter the number of lines:\n");
        scanf("%d", &n);
        if (n 20) {
            printf("out of range(1~20), please re-enter.\n");
        } else if (n % 2 == 0) { //增加是否为奇数的判断
            printf("the number is not even, please re-enter.\n");
        } else {
            *row = n;
            *col = n; //画布规模需根据问题的不同而定
            break;
        }
     } while (1);
}

void draw(canvas *p_cns) {
        int row, col;
        
        int temp_row = (p_cns->row+1)/2;
        for (row = 0; row < temp_row; row++) {
                for(col = temp_row -1-row ; col <= temp_row-1+row; col++) {
                         CANVAS_PIXEL(p_cns, row, col) = '*'; //画菱形的上半部分
                         CANVAS_PIXEL(p_cns, p_cns->row-1-row, col) = '*'; //画镜像
                }
        }
}

有了三角形输出设计的基础,我们完成菱形输出已经很easy了,关键是我们可以focus到图形生成逻辑,也就是更关注问题域了。'画布'这种概念为图形生成逻辑提供了一个很好的复用平台,而且也符合我们头脑中的平面思维逻辑。

以上问题如果用面向对象的语言来实现,用面向对象的思维(我们的那个main是否类似template method)和特性(override)就更容易实现我们的这个设计了。

年会归来

一年一度的部门年会今晚在Golden Hotel举行,这次是我入司以来参加的第四次年会,晚会和以往一样,还是很热烈、很搞笑,而我还是一如继往的’不走运’,最终抽奖连一个三等奖都和我无缘,呵呵。

记得04年参加第一次年会时,部门一共才不到七桌,那年的新员工算上我一共才8个人;而今年我们摆了20几桌,人员规模扩张了3倍多。这两年部门的效益不错,人员规模扩张的很快。考虑到人均绩效的不降低,部门今年的人才策略也许会有所调整。

一般年会的步骤都是先填饱肚子,然后再娱乐活动。大家都辛苦了一年,很不容易聚集到一起,有些同事一年出差在外达213天(试想一年的工作日一共才多少天啊),真的是很辛苦的!年会的主角还是新员工,新员工都是80后,一个一个都是很活泼的。我虽然也是80后,但是自我感觉随着岁月的打磨,自己的心理已经’老’了,总是提不起劲头儿来,而且感觉与更新的80后之间还是有很大不同的,是否存在代沟还不能确定,呵呵。

部门的一个传统就是每年新员工担纲演节目的重任,所以在此次年会的前两周,大家就开始排练节目,我们组的新员工是这次的年会表演主力,为此我也没少’发愁’,项目本来压力就大,新员工还要排练节目,耽误工作进度。还好今天的节目还是很精彩的,对得起大家辛苦的排练了。我也算是欣慰了吧。

每年部门的另一个传统都是颁发部门十大风云人物奖,这个奖每年称谓不同,今年就称为2007部门先进生产者,获奖同事每人胸前带着一朵大红花,手拿很传统的那种奖状,颇有一幅’返璞归真’的感觉,按照部长的说法,这也是’绞尽脑汁’后才想出来的法子。每年颁奖感觉有不同。今年项目组同事获得部门认可,我发自内心的替她高兴,能获得部门的认可和她自己的努力是分不开的。

年会结束,一切又都回归到现实,不再有欢声和笑语,剩下来的仍是项目压力。2008对部门来说是机遇和挑战并存的一年,对我亦然。今天部门年会的主题是"高度",我想:无论最终能否真正上到那个高度,我们从现在开始都要在各方面先从那个高度去做好准备-"站的高才能看得远"。

如发现本站页面被黑,比如:挂载广告、挖矿等恶意代码,请朋友们及时联系我。十分感谢! Go语言第一课 Go语言精进之路1 Go语言精进之路2 Go语言编程指南
商务合作请联系bigwhite.cn AT aliyun.com

欢迎使用邮件订阅我的博客

输入邮箱订阅本站,只要有新文章发布,就会第一时间发送邮件通知你哦!

这里是 Tony Bai的个人Blog,欢迎访问、订阅和留言! 订阅Feed请点击上面图片

如果您觉得这里的文章对您有帮助,请扫描上方二维码进行捐赠 ,加油后的Tony Bai将会为您呈现更多精彩的文章,谢谢!

如果您希望通过微信捐赠,请用微信客户端扫描下方赞赏码:

如果您希望通过比特币或以太币捐赠,可以扫描下方二维码:

比特币:

以太币:

如果您喜欢通过微信浏览本站内容,可以扫描下方二维码,订阅本站官方微信订阅号“iamtonybai”;点击二维码,可直达本人官方微博主页^_^:
本站Powered by Digital Ocean VPS。
选择Digital Ocean VPS主机,即可获得10美元现金充值,可 免费使用两个月哟! 著名主机提供商Linode 10$优惠码:linode10,在 这里注册即可免费获 得。阿里云推荐码: 1WFZ0V立享9折!


View Tony Bai's profile on LinkedIn
DigitalOcean Referral Badge

文章

评论

  • 正在加载...

分类

标签

归档



View My Stats