c - 检查字母表中的所有字母是否出现在没有重复的文件中
问题描述
我正在编写一个程序,该程序需要读取 CSV 文件并检查字母表中的所有字母是否在逗号的每一侧出现一次。该文件看起来像这样:
a,x
b,j
c,g
d,l
e,s
f,r
g,u
h,z
i,w
j,c
k,e
l,a
m,v
但总共会有 26 行。检查每一面是否有所有 26 个字母且没有重复的最有效方法是什么?
解决方案
虽然从您的问题和后续评论中不清楚您到底卡在哪里,或者您是否已经认输并放弃了,但让我们从头开始吧。
打开你的文件(或阅读stdin
)
在对文件内容进行任何操作之前,您需要打开文件进行阅读。对于读取格式化输入,您通常会使用使用FILE *
流指针从文件流读取和写入的函数(与低级文件描述符文件接口相反)。要打开您的文件,您将调用fopen
并检查 返回以验证打开是否成功。
不要在程序中硬编码文件名或数字。您的程序接受参数,或者传递文件名以作为参数打开,或者提示输入文件名。您可以通过将要读取的文件名作为参数来增加程序的灵活性,或者如果没有提供参数,则默认读取stdin
(就像大多数 Linux 实用程序一样)。由于是一个文件流,如果您不打开作为参数提供的文件名stdin
,您可以简单地将其分配给您的指针。FILE*
例如:
FILE *fp = NULL;
if (argc > 1) /* if one argument provided */
fopen (argv[1], "r"); /* open file with name from argument */
else
fp = stdin; /* set fp to stdin */
if (!fp) { /* validate file open for reading */
perror ("file open failed");
return 1;
}
可以使用三元运算符缩短,例如:
FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
读取您的数据
通过打开和验证文件流,您现在可以从文件中读取数据。虽然您可以使用 读取fscanf
,但如果读取的值少于两个,则它提供的信息会受到限制。scanf
此外,由于使用的转换说明符以及转换是成功还是失败,输入文件流中保留了哪些字符,因此使用函数族进行读取充满了陷阱。尽管如此,根据您的格式字符串验证两次转换的简单方法将允许您读取文件,例如
char c1, c2; /* characters from each line */
int freq1[MAXC] = {0}, freq2[MAXC] = {0}; /* frequency arrays */
...
while (fscanf (fp, " %c, %c", &c1, &c2) == 2) /* read all chars */
if (c1 > 0 || c2 > 0) /* validate ASCII values */
/* increment element in each */
freq1[(unsigned char)c1]++, freq2[(unsigned char)c2]++;
(缺点是一行中格式的任何变化都会让您存储不需要的字符,如果发生的转换少于两次,尽管有效数据仍未读取,但您的读取循环会停止)
更好的方法是使用面向行的输入函数(例如fgets
POSIX )一次读取一行getline
。使用这种方法,您一次使用一行数据,然后从存储的行中解析所需的信息。好处是显着的。您对读取本身进行了独立验证,然后您是否在该行中找到了所需的值。如果您的格式不同并且您从该行解析的值少于所需的值,您可以选择简单地跳过该行并继续下一行。此外,输入文件流中保留的内容不取决于使用的转换说明符。
一个做同样事情fgets
的例子是:sscanf
char c1, c2, /* characters from each line */
buf[MAXC] = ""; /* buffer to hold each line */
...
while (fgets (buf, MAXC, fp)) /* read all chars */
if (sscanf (buf, " %c, %c", &c1, &c2) == 2) { /* parse values */
if (c1 > 0 || c2 > 0) /* validate ASCII values */
/* increment element in each */
freq1[(unsigned char)c1]++, freq2[(unsigned char)c2]++;
}
else
fputs ("error: in line format.\n", stderr);
处理字符的频率
如果您一直关注从文件中读取数据,您会注意到在每次读取字符freq1
和时都会增加一对频率数组freq2
。正如我在上面的评论中提到的,您从一个足够大小的数组开始,int
以保存 ASCII 字符集。数组初始化为零。当您从每一列中读取一个字符时,您只需在以下位置增加值:
if (c1 > 0 || c2 > 0) /* validate ASCII values */
/* increment each element */
freq1[(unsigned char)c1]++, freq2[(unsigned char)c2]++;
例如,ASCII 值'a'
是97
(参见ASCII 表和说明)。因此,如果您阅读'a'
并递增
freq1['a']++;
这与递增相同:
freq1[97]++;
完成读取循环后,您只需从 to 迭代频率数组,'a'
文件'z'
中出现的相应字符将被捕获到数组中的次数。然后,您可以随心所欲地使用数据。
输出结果
输出 column1/column2 结果的最简单方法是输出每个字符的出现次数。例如:
for (int i = 'a'; i <= 'z'; i++) /* loop over 'a' to 'z' */
printf (" %c: %d, %d\n", i, freq1[i], freq2[i]);
这将产生类似于以下内容的输出:
$ ./bin/freq_dual_col2 <dat/char2col.txt
lowercase occurrence:
a: 1, 1
b: 1, 0
c: 1, 1
d: 1, 0
e: 1, 1
f: 1, 0
...
如果您想更详细一点并注意字符是否出现"none"
,或者1
字符是否重复"dupe"
,您可以使用一些额外的检查,例如
for (int i = 'a'; i <= 'z'; i++) { /* loop over 'a' to 'z' */
if (freq1[i] == 1) /* check col 1 chars */
printf (" %c , ", i);
else if (!freq1[i])
fputs ("none, ", stdout);
else
fputs ("dupe, ", stdout);
if (freq2[i] == 1) /* check col 2 chars */
printf (" %c\n", i);
else if (!freq2[i])
fputs ("none\n", stdout);
else
fputs ("dupe\n", stdout);
}
这将产生如下输出:
$ ./bin/freq_single_dual_col <dat/char2col.txt
lowercase single occurrence, none or dupe:
a , a
b , none
c , c
d , none
e , e
f , none
...
总而言之,您fscanf
用于阅读的最小示例可能类似于:
#include <stdio.h>
#include <limits.h>
#define MAXC UCHAR_MAX+1
int main (int argc, char **argv) {
char c1, c2; /* characters from each line */
int freq1[MAXC] = {0}, freq2[MAXC] = {0}; /* frequency arrays */
/* 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;
}
while (fscanf (fp, " %c,%c", &c1, &c2) == 2) /* read all chars */
if (c1 > 0 || c2 > 0) /* validate ASCII values */
/* increment each element */
freq1[(unsigned char)c1]++, freq2[(unsigned char)c2]++;
if (fp != stdin) fclose (fp); /* close file if not stdin */
puts ("lowercase occurrence:\n");
for (int i = 'a'; i <= 'z'; i++) /* loop over 'a' to 'z' */
printf (" %c: %d, %d\n", i, freq1[i], freq2[i]);
return 0;
}
使用fgets
and的示例sscanf
类似于:
#include <stdio.h>
#include <limits.h>
#define MAXC UCHAR_MAX+1
int main (int argc, char **argv) {
char c1, c2, /* characters from each line */
buf[MAXC] = ""; /* buffer to hold each line */
int freq1[MAXC] = {0}, freq2[MAXC] = {0}; /* frequency arrays */
/* 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;
}
while (fgets (buf, MAXC, fp)) /* read each line */
if (sscanf (buf, " %c, %c", &c1, &c2) == 2) { /* parse values */
if (c1 > 0 || c2 > 0) /* validate ASCII values */
/* increment each element */
freq1[(unsigned char)c1]++, freq2[(unsigned char)c2]++;
}
else
fputs ("error: in line format.\n", stderr);
if (fp != stdin) fclose (fp); /* close file if not stdin */
puts ("lowercase occurrence:\n");
for (int i = 'a'; i <= 'z'; i++) /* loop over 'a' to 'z' */
printf (" %c: %d, %d\n", i, freq1[i], freq2[i]);
return 0;
}
如果您想要更详细的输出,那么我将其留给您将其合并到上面的代码中。
看看事情,让我知道你是否还有其他问题。
推荐阅读
- google-apps-script - 如何将可安装的触发器副本制作成新的电子表格副本?
- java - JsonIgnore 使用开放 API 规范
- linux - 如果我想将一种测试集成到 Linux 发行版的构建管道中,我应该考虑什么?
- reactjs - 如何使用href将菜单项重定向到链接?
- node.js - include_docs 是否已从 Couchbase 节点 SDK 中删除?
- sql - 内部有 Begin End 块的 Postgresql 循环在抛出异常时被中断,同时被拦截;循环应该运行到最后
- windows - WinRM 和 Ansible 的连接问题
- c# - 如何用下拉列表过滤,我迷路了。ASP.NET
- python - 原点的主要网格线未显示在 matplotlib 中
- javascript - 在 react-native 中单击登录时如何设置状态?