C语言 打印出每个单词在给定文本文件中出现的次数

trnvg8h3  于 2023-04-11  发布在  其他
关注(0)|答案(1)|浏览(161)

我们得到了一个包含一些文本的文本文件,我们需要打印出每个单词的出现率。如果同一个单词是大写的,那就不重要了。例如:“immenso”和“IMMENSO”被认为是同一个词
我有点做过,但不是真的,在这个意义上说,它打印出比我想要的更多。问题是我不知道为什么它不工作。
正文是:

Mi illumino di immenso
Illumino di immenso
Di immenso
IMMENSO

结果应该是

immenso 4
di 3
illumino 2
Mi 1

这是我到目前为止所做的代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#define N 100

typedef struct words {
    int iteration;
    char word[N];
} word_s;

int
main()
{
    FILE *file;

    file = fopen("file", "r");

    word_s v[N];

    if (file == NULL) {
        printf("Error");
        exit(1);
    }
    int i = 0,
        j = 1,
        n = 0;
    char str[N];

    while (fscanf(file, "%s", str) != EOF) {
        if (n == 0) {
            strcpy(v[n].word, str);
            v[n].iteration = 1;
        }

        for (i = 0; i < n; i++) {
            if (stricmp(v[i].word, str) != 0) {
                strcpy(v[n].word, str);
                v[n].iteration = 1;

            }
            if (stricmp(v[i].word, str) == 0) {
                v[i].iteration++;
            }

        }

        n++;
    }
    for (i = 0; i < n; i++) {
        printf("word %s: %d iterations \n", v[i].word, v[i].iteration);
    }

    return 0;
}

基本上,我在while循环中尝试做的是,对于从fscanf(...,"%s",...);中获得的每个新单词,我们将其与已存储的单词进行比较,如果它不存在于已存储的数据结构中,则添加它,否则如果我们发现它已经存储,则递增计数器。
本来我想把n++加到这部分

if(stricmp(v[i].word,str)!=0)
        {
            strcpy(v[n].word,str);
            v[n].iteration=1;
            n++
        }

但是没有用
我得到的结果是:

Mi 1
illumino 2
di 3
immenso 4
Illumino 1
di 2
immenso 3
Di 1
immenso 2
IMMENSO 1

所以基本上它已经存储了每个单词,并从该位置开始计数。
你知道吗?

wydwbb8l

wydwbb8l1#

好了,让我们来看看一种方法,你可以通过蛮力的方式来获得一个独特的单词集合,而不需要使用二叉树或哈希表,只需要使用一个很好的旧for循环来检查所有现有的单词。
正如上面的注解中提到的,第一个单词不需要特殊情况。只需使用for循环并循环for (i=0; i<n; i++),当n为零时,将完全跳过循环。
在上面的代码中,你有另一个逻辑错误,关于n何时递增。因为你关心的是重复的单词,所以你只想在添加一个单词时递增n(数组中的单词数),而不是在每次迭代时。
还有一个可选的注意事项没有在您的问题中指定。由于您只关心不区分大小写的比较,您是否需要在转换为小写之前以原始大小写打印出单词?保存原始大小写的第二个数组是保存它的方便方法。您可以使用struct来保存strlowercase_str(并使用这些数组),或者您可以简单地使用两个2D数组,其中公共索引协调每个单词。(struct数组是更优雅的解决方案)
(Note:一个100 x 100字符数组只有10 k的存储空间--在Windows上默认的1 M堆栈最小值是微不足道的,在Linux上默认的4 M堆栈更是微不足道。在Windows上你可能有大约100个这样的字符数组...)
正如注解中提到的,stricmp()(例如strcasecmp())不是最快的函数,也不是标准C(它是POSIX)。您可以简单地使用循环来转换为小写使用tolower()并确保可移植性。(尽管几乎所有编译器都支持POSIX函数-通过适当的定义)
也就是说,您可以像下面这样更改代码,以获取文件中的唯一单词。你应该把文件名作为你程序的第一个参数(或者默认从stdin读取)。另一种方法是提示用户并读取文件名作为输入。你不需要重新编译你的代码只是为了从不同的输入文件读取。
把所有的部分放在一起,你可以做一些类似于下面的事情(可选的第二个单词数组保留输出的原始大小写),例如。

#include <stdio.h>
#include <string.h>
#include <ctype.h>

#define N 100

/* provide filename as 1st argument or read stdin by default */
int main (int argc, char **argv)
{
  int i = 0,
      n = 0,
      maxlen = 0;     /* keep longest word length for formatting output */
  char str[N],        /* buffer to hold string from file */
       lcstr[N],      /* buffer to hold lower-case string */
       words[N][N],   /* 2D array to hold 100 words of 99 char */
       lcwords[N][N]; /* 2D array to hold 100 lowercase words of 99 char */
  /* 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;
  }
  
  /* loop continually reading word up to 99-chars from file.
   * You cannot use "%s" to fill an array without protecting the array
   * bounds by using the field-width modifier. Protect your words arrays
   * by checking n < N.
   */
  while (n < N && fscanf (fp, "%99s", str) == 1) {
    int found = 0;    /* found flag */
    /* convert to lowercase in lcstr */
    for (i = 0; str[i]; i++) {
      lcstr[i] = tolower((unsigned char)str[i]);
    }
    lcstr[i] = 0;     /* don't forget to nul-terminate lcstr */
    if (i > maxlen) { /* check and save maxlen */
      maxlen = i;
    }
    /* loop checking if word exist in words */
    for (i = 0; i < n; i++) {
      if (strcmp(lcwords[i], lcstr) == 0) {
        found = 1;
        break;
      }
    }
    /* add word to words and lcwords array if not found.
     * only increment n when adding a word to array.
     */
    if (!found) {
      strcpy (words[n], str);
      strcpy (lcwords[n++], lcstr);
    }
  }
  if (fp != stdin)   /* close file if not stdin */
    fclose (fp);
  
  puts ("\nunique words:");
  for (i = 0; i < n; i++) {
    printf ("  %-*s    lowercase: %s\n", maxlen, words[i], lcwords[i]);
  }
}

两大注意事项:
(1)你没有任何需要#include <stdlib.h>的东西。只包括你需要的头文件。当你使用一个函数时--在手册页中查找它(例如在man7.org - Online Man Pages。每个函数的手册页(虽然一开始很神秘)提供了关于如何使用每个函数的简明信息--包括所需的头文件;
(2)当阅读字符到数组中时,你不能单独使用"%s"。你必须使用 field-width 修饰符来防止写入超出数组边界。在你的情况下,最多100个字符,必须将fscanf()的读取限制为99字符与"%99s"(以确保为nul-terminating字符'\0'保留1个字符的存储空间(相当于普通的0)。

示例使用/输出

使用文件dat/uniquewords.txt中的数据,您可以执行以下操作:

$ ./bin/unique_words dat/uniquewords.txt

unique words:
  Mi          lowercase: mi
  illumino    lowercase: illumino
  di          lowercase: di
  immenso     lowercase: immenso

它输出保存在words数组中的原始大小写的唯一单词。小写单词数组lcwords用于比较重复的单词。还要注意found标志是如何用于只添加未找到的单词的(您也可以使用i == n-由您决定)
如果你有问题就告诉我。

使用Array of struct添加出现次数

根据您的注解,您需要捕获出现count,您可以通过简单地将else添加到if (!found)块来实现。本质上,您的逻辑是:

/* add word to words and lcwords array if not found.
     * only increment n when adding a word to array.
     */
    if (!found) {
      words[n] = tmp;         /* assign temp struct to words */
      words[n++].count = 1;   /* set count at 1 */
    }
    else {  /* otherwise */
      words[i].count += 1;    /* increment occurrence count at i */
    }

重写以使用struct* 的 * 数组(可选地保存第一次出现的原始形式)和小写形式,您可以重新排列如下,同时添加出现count

#include <stdio.h>
#include <string.h>
#include <ctype.h>

#define N 100

typedef struct {    /* struct holding word, lowercase word and count */
  char word[N],
       lcword[N];
  int count;
} word_t;

/* provide filename as 1st argument or read stdin by default */
int main (int argc, char **argv)
{
  int i = 0,
      n = 0,
      maxlen = 0;     /* keep longest word length for formatting output */
  char str[N];        /* buffer to hold string read from file */
  word_t words[N];    /* array of struct to hold 100 words of 99 char */
  /* 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;
  }
  
  /* loop continually reading word up to 99-chars from file.
   * You cannot use "%s" to fill an array without protecting the array
   * bounds by using the field-width modifier. Protect your words arrays
   * by checking n < N.
   */
  while (n < N && fscanf (fp, "%99s", str) == 1) {
    int found = 0;                /* found flag */
    word_t tmp = { .word = "" };  /* temporary struct to fill */
    /* fill temp struct, convert to lowercase in lcword */
    for (i = 0; str[i]; i++) {
      tmp.word[i] = str[i];
      tmp.lcword[i] = tolower((unsigned char)str[i]);
    }
    tmp.word[i] = 0;    /* don't forget to nul-terminate lcstr */
    tmp.lcword[i] = 0;
    
    if (i > maxlen) {   /* check and save maxlen */
      maxlen = i;
    }
    
    /* loop checking if word exist in words */
    for (i = 0; i < n; i++) {
      if (strcmp (words[i].lcword, tmp.lcword) == 0) {
        found = 1;
        break;
      }
    }
    /* add word to words and lcwords array if not found.
     * only increment n when adding a word to array.
     */
    if (!found) {
      words[n] = tmp;         /* assign temp struct to words */
      words[n++].count = 1;   /* set count at 1 */
    }
    else {  /* otherwise */
      words[i].count += 1;    /* increment occurrence count at i */
    }
  }
  
  if (fp != stdin)   /* close file if not stdin */
    fclose (fp);
  
  puts ("\nunique words:");   /* output unique words */
  for (i = 0; i < n; i++) {
    printf ("  %-*s    count: %2d    lowercase: %s\n", 
            maxlen, words[i].word, words[i].count, words[i].lcword);
  }
}

示例使用/输出

使用相同的数据文件,输出现在将是:

$ ./bin/unique_words_struct dat/uniquewords.txt

unique words:
  Mi          count:  1    lowercase: mi
  illumino    count:  2    lowercase: illumino
  di          count:  3    lowercase: di
  immenso     count:  4    lowercase: immenso

告诉我这是否对你有用。

相关问题