当前位置: 首页 > news >正文

<< C程序设计语言第2版 >> 练习1-14 打印输入中各个字符出现频度的直方图

1. 前言

本篇文章是<< C程序设计语言第2版 >> 的第1章的编程练习1-14, 个人觉得还有点意思, 所以写一篇文章来记录下. 希望可以给初学C的同学一点参考. 尤其是自学的同学, 或者觉得以前学得不好, 需要自己补充学习的同学. 和我的很多其它文章一样, 不建议自己还没实现自己的版本, 一来就看别人的答案, 还是要自己先实现自己的版本, 实现了之后, 然后再看别人的. 这本书的权威性我无需多言, 它也有配套的习题解答, 不过, 我还没看过. 个人觉得还没有完全实现自己的版本前, 看了大师的答案也作用不大, 也吸收不了啥. 只有都自己实现过, 再看大师的版本, 你才知道你有哪些地方可以改进, 能学什么技巧. 就算自己实现的算法很烂, 效率不高, 那也没关系, 因为只有你自己实现过, 再看别人优秀的, 你才能明白自己的不足.

这道编程题原文如下:

练习 1 - 14  编写一个程序, 打印输入中各个字符出现频度的直方图.

2. 题目分析

首先目的是打印一个直方图, 是打印输入中字符出现频度的直方图. 但这个要求是很宽泛的, 大师并没有具体一些规则, 是打印水平直方图还是垂直直方图, 字符的出现个数最多统计多少个, 统计多少种字符, 这些题目并没有规定, 所以完成这道编程题很灵活, 有很多种实现. 当然大师写书时出这个题也只是为了让读者掌握基本的字符处理技巧, 所以规则也很灵活.

在这里,我的实现方式给自己加了一些规则来实现, 当然不同的人有不同的规则和实现, 灵活运用即可. 

a. 直方图为垂直直方图

b. Y轴高度即最多统计50个数量的字符, 一屏显示高度也有限, 所以要限制

c. X轴为输入中的各种字符, 但因每次输入不一样, 所以X轴字符应不固定, 随每次输入的字符的不同, 而列出不同的字符. 如果每次X轴展示的字符一样那没输入那些字符那X轴列出来也没意义, 所以X轴列出来的字符每次应不同

d. 因为屏幕宽也是有限, 所以X轴最多展示50种字符, 我们这里设定和Y轴一样, 但X轴虽然最多只展示50个字符宽, 但字符种类不局限在50个. 就是说X轴在最多50个字符宽的前提下, 展示最多97种字符, 这97个字符就是键盘主键区域的全部可见字符, 94个, 加上3个常见的不可见字符换行'\n', 制表'\t', 空格' '. 大家可以看ASCII表, 从编码 0 ~ 127, 一共就 128 个字符. 我们就统计其中的 97 个字符, 从编码 33 的 '!' 开始到编码 126 的 '~' 为止共94个. 另加3个换行制表和空格. 

3. 数据结构与算法设计思考

关于打印垂直直方图的算法的详细分析在我另外的文章中有比较完整的分析, 这里不再完整分析, 算法还和之前的一样. 即我垂直直方图从最高数量50个开始打印, 如果X轴的某个字符有当前数量行那么多,就打印直方图案, 否则打印空格符. Y轴数量最高从第50开始,依次到1为止.

这道编程题比较关键的是关于存字符数量的数据结构的思考. 

因为这道题不是打印单词长度的直方图案, 而是打印字符出现频度的直方图案, 第一反应我们还是用一个数组保存字符出现频度的数量就好, 但问题就在于在C语言中数组的下标必须是个整型或整型表达式, 统计单词的长度的时候当然单词长度可以作为数组下标, 数组的值就存放相应单词长度的数量. 那我统计字符出现频度, 数组的值可以保存字符出现的频度, 下标可以直接用字符吗? 

这本书中详细说明过关于数组的使用, 只说了数组下标必须是整型或整型表达式, 就是如 int i; str[i], 这个i就是整型, 或者 str[i + 1], 这种 i + 1 , 这里i + 1是整型表达式. 但我们不能忘了, 字符型 char 它也是个整型, 是个小整型, 它和整型是一家人. 我们从键盘输入的字符都有相应的字符编码, 字符编码它还是整型. 所以我这里考虑用两个数组来做为数据结构.

关键思考

一个数组存字符的频度数量, 可以直接用字符作为数组的下标访问数组然后存数量. 即用 str[ch]++ 这样的表达式来存字符ch的数量, 这里ch是char型, 因为数组下标不可为负, 编译器如果不指明 char类型, 可能是unsigned char可能是singed char, 为保险指定声明为 unsigned char为型. 所以就像这样统计 str['a']++, str['b']++, 这样我们就能保存各个字符自己的数量, 而且就算每次输入不一样, 我们只统计从33的'!'到126的'~'和另外3个空白符, 互相不影响, 各自存各自的. 这个存字符频度数量数组的大小的话 (就是确定数组下标范围), 虽然我们只存那97种字符, 但ASCII码表一共有128个, 所以数组只要大于128就完整覆盖. 由于为保险起见用了unsigned char, 它的整型取值范围是 0 ~ 255 , ASCII字符编码最大127, 所以unsigned char完全够用.

顺便提一下, 当然char类型它也不完全是只用于处理字符数据而设计, 因为它本身也是一种小整型, 所以很小的整型也是可以用char. 它只有 8 个 bit, 有符号就只能表示 -128到127, 无符号可以大一点从0到255. 我这里用来处理字符, 用它来覆盖ASCII编码范围有符号的就够了, 只是为防止负数下标的情况所以用无符号的char.

另一个数组的话就存每次输入的字符, 因为我加了规则每次输入不一样的字符, 展示的直方图的X轴所列出的字符也不应一样, 不然每次列一样的还展示个啥. 而且每次X轴只展示50个字符 (97种字符其中的50个), 所以这个数组每次输入不一样也存不一样的字符.

C语言强大之一也在它的字符处理能力, 尤其是和指针结合, 更是灵活多变, 功能强大. 所以用这两个数组就可以解决这个题目数据结构设计的问题.

4. 代码讲解

4.1 统计函数的说明

这个题目我想说明一下的是统计字符的函数实现:

/* 统计输入的各字符的个数 */
void countchs(char *pstr, int slen, unsigned int *pstrn, int snlen)
{int i;int ch;unsigned char c;       /* c的值为unsigned char可保证不为负数 */ int counted;i = 0;    counted = 0;while ((ch = getchar()) != EOF) {if (ch >= '!' && ch <= '~') {  /* 先统计字符数量再统计种类 */if (pstrn[ch] < snlen) {   /* 最多50个字符数 */pstrn[ch]++; counted = 1;}} else if (ch == ' ') {        /* 三个常见空白字符也统计 */pstrn[' ']++;counted = 1;} else if (ch == '\t') {pstrn['\t']++;counted = 1;} else if (ch == '\n') {pstrn['\n']++;counted = 1;}if (counted == 1 && i < slen) { /* 统计97种字符种类 */for (i = 0; pstr[i] != '\0' && pstr[i] != ch; i++) { ; }pstr[i] = ch; counted = 0;} }
}

while循环中就两个if块, 第1个if语句块统计我们要统计的97种字符, 从 '!'到'~' (这中间包括了大小写字母和数字字符), 另加3个换行制表空格符. 我们直接用字符或unsigned char作为下标存它自己的数量. 这里pstrn[' ']和pstrn['\t']因为字符编码仍然是ASCII码0~127整型所以合法, 而ch是unsigned char范围是0 ~ 255也是整型也合法.

后面那个数组pstr[i]我们存每次输入的字符, 如果在我们前面if语句中已统计过的97个字符范围内, 每获得1个新出现的还没存的字符就存到数组中的第1个空位处. 即pstr[i] == '\0'的位置处. i这里用来找存放的位置.并且i控制字符种类上限. 如果没出现过才存新字符. (其实这里不可能到上限97种字符,因为X轴最多打印50个)

4.2 X轴长度变化的说明

因为打印X轴最多50个字符所以我们用 i < snlen, 这个snlen就是50来控制. 如果不足50个字符, 就按实际有多少种输出X轴, 所以X轴打印结束条件有个pstr[i] != '\0'. 因为到存字符种类那个数组为空字符 '\0'时说明打完了.

    for (i = 0; pstr[i] != '\0' && i < snlen; i++) {putchar('-');putchar('-');}

好了本文章我想写的重点就在这两个数组处理的问题上. 打印垂直直方图的算法有兴趣的同学可以看看我的另几篇文章, 算法一样的. 仅供参考, 也有bug, 代码测试也不充分, 请见谅.

4.3 X轴字符显示空格制表换行符

因为空格制表和换行符是不可见字符,但又在统计中, 所以将空格打印为\s, 制表打印为\t, 换行打印为\n, 为可见方便查看.

5. 完整代码

/* C程序设计语言第2版 练习1-14 打印输入中各字符出现频度的直方图 */
#include <stdio.h>
#define MAXNUM 50   /* 最多只统计50个字符数量 */ 
#define MAXCH 97    /* 只统计94种可见字符和2个制表空格换行符 */
#define MAX 128     /* ASCII共128个字符 */void initchs(char *pstr, int slen, unsigned int *pstrn, int snlen);
void countchs(char *pstr, int slen, unsigned int *pstrn, int snlen);
void printhtgm(char *pstr, int slen, unsigned int *pstrn, int snlen);
void putspaces(int n);
int main(void)
{char strs[MAXCH];                         /* 存放字符       */unsigned int strsnum[MAX];                /* 存放字符数量   */initchs(strs, MAXCH, strsnum, MAX);       /* 初始化字符数组 */    countchs(strs, MAXCH, strsnum, MAXNUM);   /* 统计字符       */printhtgm(strs, MAXCH, strsnum, MAXNUM);  /* 打印直方图     */return 0;
}/* 初始化统计数组 */
void initchs(char *pstr, int slen, unsigned int *pstrn, int snlen)
{int i;for (i = 0; i < slen; i++) { pstr[i] = '\0';}for (i = 0; i < snlen; i++) { pstrn[i] = 0; }
}/* 统计输入的各字符的个数 */
void countchs(char *pstr, int slen, unsigned int *pstrn, int snlen)
{int i;int ch;unsigned char c;       /* c的值为unsigned char可保证不为负数 */ int counted;i = 0;    counted = 0;while ((ch = getchar()) != EOF) {if (ch >= '!' && ch <= '~') {  /* 先统计字符数量再统计种类 */if (pstrn[ch] < snlen) {   /* 最多50个字符数 */pstrn[ch]++; counted = 1;}} else if (ch == ' ') {        /* 三个常见空白字符也统计 */pstrn[' ']++;counted = 1;} else if (ch == '\t') {pstrn['\t']++;counted = 1;} else if (ch == '\n') {pstrn['\n']++;counted = 1;}if (counted == 1 && i < slen) { /* 统计97种字符种类 */for (i = 0; pstr[i] != '\0' && pstr[i] != ch; i++) { ; }if (pstr[i] == '\0') { pstr[i] = ch; } counted = 0;} }
}/* 显示各字符出现频率的直方图 */
void printhtgm(char *pstr, int slen, unsigned int *pstrn, int snlen)
{int i, j;putchar('\n');putchar('\n');putspaces(4);putchar('Y');putchar('\n');putspaces(4);putchar('^');putchar('\n');for (j = snlen; j > 0; j--) {   /* 打印Y轴和直方图案 */putspaces(2);printf("%2d|", j); for (i = 0; pstr[i] != '\0' && i < snlen; i++) {if (pstrn[pstr[i]] >= j) {putspaces(1);putchar('*');} else {putspaces(2);}}putchar('\n');}putspaces(4);                   /* 打印X轴,X轴是可变化长度不固定 */putchar('+');                  for (i = 0; pstr[i] != '\0' && i < snlen; i++) {putchar('-');putchar('-');}putchar('>');putspaces(1);putchar('X');putchar('\n');putspaces(5);/* X轴字符按实际字符数打印 *//* 最多50个字符 */for (i = 0; pstr[i] != '\0' && i < snlen; i++) { if (pstr[i] == ' ') {         /* 打印不可见字符为可见字符 */putchar('\\');putchar('s');} else if (pstr[i] == '\t') {putchar('\\');putchar('t');} else if (pstr[i] == '\n') {putchar('\\');putchar('n');} else {                     /* 其余可见字符正常打印 */putspaces(1);                            putchar(pstr[i]);}}putchar('\n');putchar('\n');
}void putspaces(int n)
{int i;for (i = 0; i < n; i++) { putchar(' '); }
}

6. 运行结果

输入 歌曲 << my love >> 部分歌词结果

输入歌曲 << my hear will go on >> 部分歌词结果

当然有兴趣的同学也可以改进把Y轴也可以变为不固定高度.

7. 结语

您的点赞和收藏是我创作的动力, 感谢.

相关文章:

  • 分布式事务知识点整理
  • ConvSearch-R1: 让LLM适应检索器的偏好或缺陷
  • 【LUT技术专题】极小尺寸LUT算法:TinyLUT
  • 宽松相等(==) 的转换规则(仅考虑基本数据类型)
  • Jouier 普及组十连测 R4
  • Python入门手册:Python基础语法
  • Python邮件处理:POP与SMTP
  • C++构造和折构函数详解,超详细!
  • YOLO11解决方案之速度估算探索
  • 第R7周:糖尿病预测模型优化探索
  • 【Python打卡Day30】模块与包的导入@浙大疏锦行
  • 《P1470 [USACO2.3] 最长前缀 Longest Prefix》
  • Python打卡5.23(day24)
  • Jouier 普及组十连测 R3
  • Claude 4 发布:编码 AI 新纪元的开启
  • 文章记单词 | 第111篇(六级)
  • 文章记单词 | 第114篇(六级)
  • 生成模型——变分自动编码器(Variational Autoencoders, VAEs)
  • 国家网络身份认证公共服务管理办法
  • LEED认证是什么?LEED认证难吗?LEED认证需要准备的资料
  • 中秋网页设计素材网站/市场调研的四个步骤
  • 广州天河做网站/抖音搜索seo
  • 德州网站优化/徐州seo外包平台
  • wamp 做网站发布/优化师培训
  • 婚车网站模版/看网站搜什么关键词
  • 阜阳网站建设价格低/做公司网页