检查是否所有字母都出现在文件中且没有重复

时间:2019-02-04 23:35:50

标签: c arrays loops csv

我正在编写一个程序,该程序需要读取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个字母都没有重复的最有效方法是什么?

2 个答案:

答案 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)读取一行。使用这种方法,您一次要消耗一行数据,然后从存储的行中解析所需的信息。好处是巨大的。您可以对读取本身进行独立验证,然后可以在行中是否找到所需的值。如果您的格式有所不同,则您解析的行数少于所需的值,则可以选择跳过该行并继续下一行。此外,输入文件流中剩余的内容并不取决于所使用的转换说明符。

fgetssscanf做同样事情的例子是:

    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);

处理字符频率

如果您一直在注意从文件中读取数据,则会注意到每次读取字符freq1freq2时都会增加一对频率阵列。正如我在上面的评论中所提到的,您从一个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;
}

使用fgetssscanf的示例类似于:

#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)

将所有列添加到集合中,并检查集合是否与文件行大小相同。

  • 请记住,集合会忽略重复项