1. 编程要求
多年前写过一篇用 C 语言实现打印单词字符数量统计的直方图的文章, 现在看上去有些混乱, 对一些任务划分不清晰, 全部混在一起. 于是重写了这个编程题, 希望可以给初学者一些参考, 并且我分别用 C, C++, Java, Python 四种语言完成了这道编程题, 有兴趣的可以看我另外三篇文章用 C++ / Java / Python 实现的. 当然, 没有标准答案, 只能是参考. 原题是<<C程序设计语言第2版>>练习1-13的编程题. 原文如下:
练习1-13 编写一个程序, 打印输入中单词长度的直方图. 水平方向的直方图比较容易绘制, 垂直方向的直方图则要困难些.
我这里实现的是 垂直 方向的, 对于初学者来说学习语言只要时间和精力允许, 则尽可能的挑战难一点的要求去完成. 如果你还能用 3 种及以上的方式(指的是同一种语言)完成编程要求, 那更好
我发现如果一篇文章一上来就扔一堆代码, 文字讲解太少的话, 没什么人愿意有耐心的看, 尤其是关键部分的算法讲解太少的话. 所以我这里还是对这个小小编程题的核心算法部分描述一下. 其它三篇 C++ / Java / Python 的核心部分都是使用的相同的算法. 所以我这里核心的那一点算法描述都是一样的, 只是用不同语言去实现而已. OK, 核心部分并且也是实现垂直直方图的难点的部分的算法如下:
2. 算法设计
直方图是竖着显示单词的数量,而不是横着显示,可程序只能一行一行打印,这里是打印垂直直方图的一个难点.那我们怎么确定什么时候该打印图案,什么时候不打印图案呢?
关键算法
我们定义一个 words 数组下标代标表示单词长度, 数组值表示对应下标长度值的单词数量.可以发现直方图Y轴部分刚好和行号相对应,在我们统计的单词数组中的单词数量刚好可以和行号对应,比如长度为 2 的单词我们统计有 5 个,那我们打印Y轴最少要打印 5 行,同时从第 5 行并与之对应的单词长度的列上就要开始打印图案.
如何实现
我们通过一个双层循环,外层控行最大行数,内层用迭代变量例如 j 来访问单词数组 words[] .比如在第 1 列就是 words[j] 上与当前行号例如第 6 行比对,如果 words[j] >= 6 为真则说明j长度的单词还有 6 个没打印,则从当前第 6 行就要开始打印图案. 这样依次打印第 6 行所有长度(列) j = 6 ~ 1,然后下一轮进入第 5 行打印有 5 个单词数量(只要单词数量pw[j] >= i行号, 打印过第6行的那一列在5, 4, 3, 2, 1行继续打印)的相应单词长度的数量,一直到长度为1. (注: 下面用指针 pw 访问words数组)
所以, 关键算法部分转换成代码就这一点点:
/*打印Y轴和直方图案*/
for (i = len - 1; i > 0; i--) { /*i控制直方图高度即行数*/
printf("%3d|", i); /*打印Y轴量度值和Y轴*/
for (j = 1; j < len; j++) { /*j作下标来访问words数组中单词数量*/
if (pw[j] >= i) { /*如果当前长度单词字符数不少于行号这么多数量*/
printf("%3c", '*'); /*则打印直方图图案*/
} else { /*否则当前长度单词数打印空白*/
printf("%3c", ' ');
}
3. 完整代码
/* << C程序设计语言第二版 >> 练习1-13 实现打印输入中单词长度的直方图*/
/* 本程序实现的是垂直方向的直方图 */
#include <stdio.h>
#include <ctype.h>
#define MAXLEN 10
/*初始化单词数量数组*/
void InitWords(int (*pw)[MAXLEN]);
/*完成各单词长度的统计*/
void CountWord(int *pw, int len);
/*根据words数组信息打印直方图*/
void PrintHisto(int *pw, int len);
int main()
{
int words[MAXLEN]; /*存放各长度的单词数量,下标即单词长度*/
InitWords(&words); /*初始始化单词数量数组*/
CountWord(words, MAXLEN); /*统计输入的各长度单词数量*/
PrintHisto(words, MAXLEN); /*根据words数组信息打印直方图*/
printf("\nDone.\n\n");
return 0;
}
/*初始化单词数量数组*/
/*pw是一个数组指针,这里传给pw的是数组words的地址*/
/*是传址,不是常见的传值,当然无论传值还是传址都可以*/
/*这个数组指针有点像函数指针,只不过右边是方括号*/
void InitWords(int (*pw)[MAXLEN])
{
int i;
for (i = 0; i < MAXLEN; i++) {
(*pw)[i] = 0; /*pw是指向数组的指针,*pw代表数组*/
}
}
/*完成各单词长度的统计*/
void CountWord(int *pw, int len)
{
char ch;
int wordin = 0; /*是否在单词中的状态*/
int chcount = 0; /*统计单词的长度*/
printf("\nEnter some words(# to quit): \n");
while ((ch = getchar()) != '#') {
if (isalpha(ch)) { /*如果是字母*/
if (0 == wordin) { wordin = 1; } /*不在单词内则置为进入单词*/
if (chcount < len - 1) { chcount++; } /*如果当前单词不超长则计数*/
} else { /*如果是非字母字符*/
if (1 == wordin) { /*如果在单词中则*/
wordin = 0; /*置为出单词状态*/
pw[chcount]++; /*当前长度单词数加1*/
chcount = 0; /*重置单词字符个数*/
}
}
}
pw[chcount]++; /*统计最后一个单词*/
}
/*根据words数组信息打印直方图*/
/*我们将打印直方图分成两大部分*/
/*第一部分是打印Y轴和直方图图案,第二部分是打印X轴及下方*/
void PrintHisto(int *pw, int len)
{
int i, j;
/*第一部分,打印Y轴及直方图图案*/
printf("\n Y The count of word\n");
printf("%4c\n", '|');
/*打印Y轴和直方图案*/
for (i = len - 1; i > 0; i--) { /*i控制直方图高度即行数*/
printf("%3d|", i); /*打印Y轴量度值和Y轴*/
for (j = 1; j < len; j++) { /*j作下标来访问words数组中单词数量*/
if (pw[j] >= i) { /*如果当前长度单词字符数不少于行号这么多数量*/
printf("%3c", '*'); /*则打印直方图图案*/
} else { /*否则当前长度单词数打印空白*/
printf("%3c", ' ');
}
}
printf("\n");
}
/*第二部分,打印X轴及量度值*/
printf(" +");
for (i = 0; i < len; i++) { /*X轴*/
printf("---");
}
printf(">X The length of word\n");
printf(" 0 "); /*打印原点*/
for (i = 1; i < len; i++) { /*X轴数字*/
printf("%3d", i);
}
printf("\n");
}
4. 运行结果
5. 结语
写文章不容易, 支持原创, 如转载请注明出处. 喜欢就点赞收藏, 您的点赞收藏是我创作的动力. 祝我的文章读者工作顺利. 谢谢.