希望大家都好!我有点困在我的编程作业,c代码的目的是比较一个txt文件(名称作为用户输入)另一个关键字txt文件.然后记录每个单词的频率并按降序打印它们.这应该看起来像这样:
euery 8
common 8
gaue 7
thankes 5
vnkle 4
growes 3
wag 3
seal 3
day 3
soft 3
字符串
代码如下所示:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_FILENAME_LENGTH 256
#define MAX_TEXT_LENGTH 100000
#define MAX_KEYWORDS 100
// Structure to store keyword frequencies
typedef struct KeywordFrequency {
char keyword[50];
int frequency;
} KeywordFrequency;
// Compare function for sorting keyword frequencies
int compareKeywords(const void *a, const void *b) {
return ((KeywordFrequency *)b)->frequency - ((KeywordFrequency *)a)->frequency;
}
int main() {
char filename[MAX_FILENAME_LENGTH];
char text[MAX_TEXT_LENGTH];
KeywordFrequency keywords[MAX_KEYWORDS];
int totalKeywords = 0;
// Ask the user for the name of the text file
printf("Enter the name of the text file: ");
scanf("%s", filename);
// Open and read the text file
FILE *textFile = fopen(filename, "r");
if (textFile == NULL) {
printf("Error opening %s\n", filename);
return 1;
}
// Read the content of the text file
fread(text, 1, sizeof(text), textFile);
fclose(textFile);
// Open and read the keyword file
FILE *keywordFile = fopen("keyword.txt", "r");
if (keywordFile == NULL) {
printf("Error opening keyword.txt\n");
return 1;
}
// Initialize keyword frequencies
while (fscanf(keywordFile, "%s", keywords[totalKeywords].keyword) != EOF) {
keywords[totalKeywords].frequency = 0;
totalKeywords++;
}
fclose(keywordFile);
// Tokenize and compare the text with keywords
char *token = strtok(text, " ");
while (token != NULL) {
for (int i = 0; i < totalKeywords; i++) {
if (strcmp(token, keywords[i].keyword) == 0) {
keywords[i].frequency++;
}
}
token = strtok(NULL, " ");
}
// Sort keyword frequencies in descending order
qsort(keywords, totalKeywords, sizeof(KeywordFrequency), compareKeywords);
// Print the results in a table format
printf("Keyword\tFrequency\n");
for (int i = 0; i < totalKeywords; i++) {
printf("%s\t%d\n", keywords[i].keyword, keywords[i].frequency);
}
return 0;
}
型
enter image description here
任何帮助都将不胜感激
2条答案
按热度按时间6jjcrrmo1#
正如@someprogrammerdude在他的回答中指出的那样,使用
"%s"
只会读取一个空格分隔的单词。为了将整行文本读入足够大的缓冲区,fgets()
是首选函数。(事实上,这是阅读文本或用户输入的首选方式)。从fgets()
和sscanf()
填充的缓冲区中(分离)您需要的值。与直接使用fscanf()
相比,这提供了几个好处。它简化了读取和解析,并允许对每个步骤进行单独验证。继续我的评论,不要使用 Magic-Numbers。
50
在你的结构定义中是一个 Magic-Number。你用#define
声明常量做得很好,只需要为keyword
成员数组定义一个常量。你用MAX_TEXT_LENGTH
也做得很好 * 不忽略缓冲区大小 *,但是你可以为手头的文件定制更窄一点。一般来说,行不超过1 K左右。100 K当然涵盖了这一点,但是在嵌入式或内存有限的系统上,这对于读缓冲区来说可能有点多。对于这里的行,100 bytes就可以了。(这仍然是最长行的10倍)所以下面的代码就可以了:字符串
(注意:
limits.h
提供了PATH_MAX
定义,给出了系统的最大路径名长度,这是一种方便的方法来调整filename
数组的大小)现在,为了消除 Magic-Number,只需将
KeywordFrequency
结构体定义为:型
虽然你的变量声明是好的,但初始化所有变量和数组是一个好习惯(特别是在学习C语言时),当变量的值不确定时,防止无意中访问具有自动存储持续时间的变量(a/k/a Undefined Behavor)如果可能的话,通过在作用域的开始定义变量,可以确保代码可移植到所有C语言标准。一个轻微的重新排列可能是:
型
所有用户输入都应该使用
fgets()
,以确保一次读取一整行(包括用户按Enter键生成的'\n'
)。这样,在你的输入流中是否还有额外的字符是毫无疑问的(例如stdin
)unread --只是在等待下一次读取尝试时咬你。要删除fgets()
读取并包含在fgets()
填充的缓冲区中,简单地使用strcspn()
,如下所示。您的输入可以视为:型
现在打开你的文件并输入你的 read-loop 阅读你的文件内容到你的
struct KeywordFrequency
数组中。注意:你总是想在你的 read-function 返回时设置你的 read-loop。在这里你可以这样做:型
(*注意:
剩下的就是用
qsort()
排序了。根据我的评论,你想重新排列你的compare()
函数,这样它就可以返回条件表达式的结果而不是减法。为什么?你可以很容易地溢出减去两个大的负数。使用比较可以避免这种可能性,例如。型
你的基本代码表明你正在努力学习C的正确方法。这是学习C的唯一方法。否则,你会加倍学习时间来打破坏习惯,学习你应该在第一次学习的东西。保持良好的工作。完整的例子如下所示:
型
示例使用/输出
编译上面的代码(在编译所有程序时使用 *Full Enhanced *),然后以您的数据文件作为输入运行程序,您将获得:
型
(**注意:**你也可以按频率排序,如果频率相等,只需要几行额外的代码)
把东西看一遍,如果有问题就告诉我。
**=
好的,所以你的问题中的文件是一个示例输出文件,而不是输入文件--明白了。
以下是所需更改的简短摘要:
使用
fscanf (fp, "%s", word)
是一种很好的方式来阅读 * 一次一个单词 *(使用适当的 * 字段宽度 * 修饰符)。而不是分配100 K的结构,只是分配2到开始,然后
realloc()
当你填写你所分配的。与您的115页文本,每次需要realloc()
时简单地将大小加倍只需要17个realloc()
调用来容纳文件。(在大多数情况下,它是内存增长和需要重新分配的次数之间的一个很好的平衡)一旦你读了一个单词,你需要一个函数来检查它是否已经存在于你的单词集合中,如果存在,只需将频率增加1,如果没有,检查你的集合中是否有空间添加单词,如果没有
realloc()
,然后添加单词并将频率设置为1。"%s"
不能区分"[The"
和"The"
,所以你应该根据需要检查并删除标点符号。像"yours"
,"yours,"
,"yours."
,"yours:"
和"yours;"
(都在你的输入文件中)。一个简单的函数,从buf
中去掉前导和尾随标点符号就可以了。你有几个很长的连字符的单词要处理,例如
"Pastoricall-Comicall-Historicall-Pastorall"
和"Tragicall-Comicall-Historicall-Pastorall"
。没有明确的规则来处理连字符的单词。它们是完全有效的,在你的输入文档的上下文中更是如此。一个选项是保持最大单词长度,然后动态调整输出间距以容纳列表中最长的单词。您可以使用
printf()
字符串中的'*'
来实现这一点,提供最大单词长度变量来指定关键字字段的 * 最小字段宽度 *。有了这个前提,我就可以很容易地修改你的代码来做需要做的事情。(而不是修改,我只是重写了它,我不喜欢 camelCase 变量名,我尽量保持名称简短但具有描述性--因为我不喜欢键入
:)
你可以做类似的事情:
型
**注意:**我添加了
ALPHASORTEQUAL
的预处理器条件,以允许您以相等的频率对单词进行排序。预处理器条件只是允许您根据是否定义了代码来有条件地 * 包含 * 或 * 排除 * 代码。例如,什么都不做将排除按顺序排序的代码,但添加定义(无论是在代码中)还是在编译字符串中,例如-DALPHASORTEQUAL
用于gcc/clang或/DALPHASORTEQUAL
用于VS将 * 包含 * 代码。启用全功能编译
我在最初的回答中提到了这一点,但没有给予示例。使用
gcc
,包含-Wall -Wextra -pedantic -Wshadow
的编译字符串将启用接近所有警告(绝对涵盖了所有学习C代码的案例)。添加-Werror
将警告视为错误(所以不能作弊)。不要接受代码,直到它编译没有警告。对于VS编译器(cl.exe
)使用/W3
就可以了。当出现警告时,阅读并理解每个警告-然后修复它。警告将识别任何问题,以及它们发生的确切行。你可以通过简单地倾听编译器告诉你的东西来学习很多东西。在过去的20年里,他们已经非常非常擅长提供描述性警告。
我在这段代码中使用的编译字符串示例:
型
如果单词频率相等,要启用字母排序,只需定义
ALPHASORTEQUAL
,例如。型
(**注意:**我把数据文件放在当前目录下的
dat/
目录下,把可执行文件放在当前目录下的bin/
目录下,以保持当前目录下的C源代码文件与其他东西的直接整洁。这就是为什么你在编译字符串中看到-o bin/...
,在下面看到dat/...
的文件名读取)示例使用/输出
**注意:**如果您尝试将google-docs中的文件保存为 Plain Text,则会将其保存为DOS格式的BOM(字节顺序标记)。如果在Linux上,请运行
dos2unix filename.txt
将其转换为Unix行尾并删除BOM。对于
dat/word-freq-main.txt
中的转换文件,您只需将其作为第一个命令行参数传递给程序,例如。型
内存使用/错误检查
在你写的任何动态分配内存的代码中,你有两个关于任何内存块的责任:(1)总是为内存块保留一个指向起始地址的指针,(2)当它不再需要时,它可以被释放。
必须使用内存错误检查程序来确保您不会尝试访问内存或写入超出/超出已分配块的范围,尝试读取或基于未初始化的值进行条件跳转,最后确认您已释放所有已分配的内存。
对于Linux,
valgrind
是正常的选择。每个平台都有类似的内存检查器。它们都很容易使用,只需通过它运行您的程序。型
始终确认您已释放了所有已分配的内存,并且没有内存错误。
如果你还有问题就告诉我。
sq1bmfud2#
两件事:
第一个是
scanf``%s
读的是 * 空格分隔的**单词 *。所以没有什么需要标记的。你甚至从来没有读过任何你试图标记的东西到text
中;第二件事是你把输入文件中的每一个单词都读到一个单独的“keyword”元素中,这不是你应该做的。如果输入文件中有超过100个单词,那么就会超出数组的界限。
相反,你应该读入另一个变量并搜索当前的关键字,看看它是否在数组中,如果是,则增加频率,否则添加它。