我正在编写一个程序,该程序需要读取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个字母都没有重复的最有效方法是什么?
答案 0 :(得分:1)
虽然您的问题和后续评论尚不清楚您确切地卡在哪里,或者您是否已经放弃并放弃了,但让我们从头开始。
打开文件(或阅读stdin
)
在对文件内容执行任何操作之前,需要打开文件进行读取。对于读取格式化输入,通常将使用使用FILE *
流指针(与低级 file-descriptor 文件接口相对)从文件流读取和写入的函数。要打开文件,您将调用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;
}
,可以使用 ternary 运算符将其缩短,例如:
FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
读取数据
打开文件流并 已验证 ,您现在可以从文件中读取数据了。虽然您可以使用fscanf
进行读取,但是如果读取的值少于两个,则您提供的信息将受到限制。此外,由于使用的转换说明符以及转换成功与失败,输入文件流中还保留哪些字符,因此使用scanf
函数系列进行读取时充满了陷阱。尽管如此,一种简单的方法可以根据您的 format-string 验证两次转换,从而使您可以读取文件,例如
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]++;
例如,'a'
的ASCII值为97
(请参见ASCII Table and Description)。因此,如果您阅读'a'
并递增
freq1['a']++;
与递增相同:
freq1[97]++;
完成读取循环后,您只需要从'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
和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;
}
如果您想要更详细的输出,那么我留给您将其合并到上面的代码中。
仔细检查一下,如果您还有其他问题,请告诉我。
答案 1 :(得分:0)
将所有列添加到集合中,并检查集合是否与文件行大小相同。