当您需要计算集合中任何元素在给定集合中出现的次数时,最基本的方法之一是使用频率数组。很简单,一个频率数组包含了一个集合中可以出现的元素数量。 (例如,映射到每个索引的事物发生的频率是多少?)
例如,如果您想知道任何一个字母字符在一行文本中出现的次数(忽略大小写),您的集合有 26 个元素(字母表中的字母)。因此,您只需声明一个由 26 个元素组成的数组(通常是 int 或 size_t),初始化全部为零,然后循环遍历文本行中的字符,并为每个 alpha-字符,你增加数组的相应元素。
例如,对于数组int chars[26] = {0}; 和包含"Dog" 的行,您只需递增:
chars[toupper(line[0]) - 'A']++; /* increments index chars[3]++ */
chars[toupper(line[1]) - 'A']++; /* increments index chars[14]++ */
chars[toupper(line[2]) - 'A']++; /* increments index chars[6]++ */
(请参阅ASCII Table and Description 以了解toupper(line[0]) - 'A' 如何将字符映射到相应的索引。另请注意转换为unsigned char 为清楚起见故意省略)
遍历完行中所有字符后,可以通过遍历频率数组的所有元素来输出结果,如果某个索引处的值大于零,则输出该索引对应的字符和该值作为计数持有,例如
for (int i = 0; i < 26; i++)
if (chars[i])
printf ("[%c] => %d\n", i + 'A', chars[i]);
要处理行中的数字字符,您只需使用第二个 10 元素频率数组并执行相同操作,但这次减去 '0' 以将 ASCII 数字映射到数组的元素 0-9。
您忽略的问题的另一个方面是验证每个输入和转换。除非您检查返回以确定操作是成功还是失败,否则您无法正确使用任何输入函数。这同样适用于对数字类型的每次转换以及对代码的持续定义操作至关重要的任何其他表达式。
将这些部分放在一起,您可以使用全局 enum 为您的程序定义所需的常量(或使用多个 #define 语句定义同一事物)并打开您的文件并读取第一行并将其转换为整数价值:
#include <stdio.h>
#include <ctype.h>
enum { DIGITS=10, CHARS=26, MAXC=1024 }; /* constants for use in program */
int main (int argc, char **argv) {
char line[MAXC]; /* buffer to hold each line of input */
int ncases = 0; /* integer for number of cases */
/* use filename provided as 1st argument (stdin by default) */
FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
if (!fp) { /* validate file open for reading */
perror ("file open failed");
return 1;
}
if (!fgets (line, MAXC, fp)) { /* read/validate 1st line */
fputs ("(user canceled input)\n", stdout);
return 0;
}
if (sscanf (line, "%d", &ncases) != 1) { /* convert/validate to int */
fputs ("error: invalid integer input for ncases.\n", stderr);
return 1;
}
现在有了ncases 中的案例数,您只需循环该次数,从文件中读取下一行,循环遍历line 中的每个字符,递增两个频率数组chars 的相应元素和digits 计算每种字符类型(alpha 和digits)的出现次数,然后依次输出在每个频率数组上循环的结果,以输出每个字符类型的计数。你可以这样做:
for (int i = 0; i < ncases; i++) { /* loop ncases times */
int chars[CHARS] = {0}, /* frequency array for characters */
digits[DIGITS] = {0}; /* frequency array for digits */
if (!fgets(line, MAXC, fp)) { /* read/validate line */
fputs ("error: reading line from file.\n", stderr);
return 1;
}
for (int j = 0; line[j]; j++) { /* loop over each char in line */
int c = toupper((unsigned char)line[j]); /* convert to uppercase */
if (isalpha((unsigned char)c)) /* check if A-Z */
chars[c-'A']++; /* increment chars at index */
else if (isdigit((unsigned char)c)) /* check if 0-9 */
digits[c-'0']++; /* increment digits at index */
}
printf ("\nCase #%d:\n\n", i + 1); /* output case no. */
for (int j = 0; j < CHARS; j++) /* loop over chars array */
if (chars[j]) /* if value at index non-zero */
printf ("[%c] => %d\n", j + 'A', chars[j]); /* output count */
for (int j = 0; j < DIGITS; j++) /* loop over digits array */
if (digits[j]) /* if value at index non-zero */
printf ("[%d] => %d\n", j, digits[j]); /* output count */
}
(注意: 用于输出位数,无需将索引映射回它们的 ASCII 值——您可以简单地使用 %d 输出整数表示,而不是映射 @987654344 @ 输出带有 %c 的 ASCII 字符 - 真的是你的选择。另请参阅 man 3 isalpha 以了解任何分类宏的每个参数必须为 unsigned char 类型的要求 - 解释上述转换的目的(unsigned char))
这基本上是解决频率阵列问题的完整代码。您所需要的只是整理并将成功返回给shell,例如
if (fp != stdin) /* close file if not stdin */
fclose (fp);
return 0;
}
使用/输出示例
将您的示例输入放在文件 dat/charcountlines.txt 中,然后将其作为输入提供给程序,将产生以下结果:
$ ./bin/charcount dat/charcountlines.txt
Case #1:
[A] => 4
[B] => 1
[E] => 1
[I] => 3
[N] => 4
[R] => 2
[S] => 2
[T] => 2
[U] => 2
[V] => 1
[Y] => 1
Case #2:
[B] => 1
[I] => 1
[N] => 1
[S] => 1
[U] => 1
[0] => 2
[2] => 2
Case #3:
[A] => 1
[C] => 2
[D] => 1
[I] => 2
[N] => 1
[O] => 3
[R] => 2
[S] => 1
[U] => 1
[V] => 2
[1] => 1
[9] => 1
以上匹配您指定的输出和排序。如果您还有其他问题,请仔细查看并告诉我。