首页 > 解决方案 > 检查字母表中的所有字母是否出现在没有重复的文件中

问题描述

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

标签: carraysloopscsv

解决方案


虽然从您的问题和后续评论中不清楚您到底卡在哪里,或者您是否已经认输并放弃了,但让我们从头开始吧。

打开你的文件(或阅读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]++;

(缺点是一行中格式的任何变化都会让您存储不需要的字符,如果发生的转换少于两次,尽管有效数据仍未读取,但您的读取循环会停止)

更好的方法是使用面向行的输入函数(例如fgetsPOSIX )一次读取一行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;
}

使用fgetsand的示例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;
}

如果您想要更详细的输出,那么我将其留给您将其合并到上面的代码中。

看看事情,让我知道你是否还有其他问题。


推荐阅读