【C语言】交叉引用生成器练习题
写在前面的话:我本人也是学生,难免会出错,所以如有错误或更好的建议可以在下方留言。
这周编程课留了一道名为“交叉引用生成器”的题目,题目有点长,做起来也确实费了我不少事,题目如下:
样例输入如下:
Alcatel provides end-to-end solutions.
It enables enterprises to deliver content to any type of user.
lcatel operates in 130 countries.
Alcatel focus on optimizing their service offerings and revenue streams
样例输出如下:
Alcatel:1,4
It:2
any:2
content:2
countries:3
deliver:2
enables:2
end-to-end:1
enterprises:2
focus:4
in:3
lcatel:3
of:2
offerings:4
on:4
operates:3
optimizing:4
provides:1
revenue:4
service:4
solutions:1
streams:4
their:4
to:2
type:2
user:2
乍一看好像并没有什么思路,既不知道输入的行数,也不知道每一行单词的个数,这可如何是好......?
目前我们可以确定的是使用结构体,将每个独立成块的字符串、它对应的行数以及一共出现过几次存储起来,同时剔除标点符号,然后逐一判断这是不是一个合法的单词,再进行排序,最后输出。整体思路确定好了,接下来就是一步一步的实现了。结构体如下:
struct edge {
char word[21];
int lines[201], numnow;
};
(这里的行数lines可以直接使用set容器,避免了后续的筛重过程。结构体也可以替换成链表,只是比较麻烦)
1、将文件内容读取到程序里
条件这么棘手,看来只能使用失传已久的江湖绝学while循环+判断字符了。
首先,我们需要知道,在以文件形式进行输入的时候,如果使用while循环,当输入函数(fscanf,fgetc,scanf等)读取到文件末尾时会返回EOF(end of file),而EOF在stdio.h头文件中已有定义(如下)。
int __cdecl fgetc(FILE *_File);
int __cdecl fscanf(FILE * __restrict__ _File,const char * __restrict__ _Format,...) __MINGW_ATTRIB_DEPRECATED_SEC_WARN;
因此我们可以使用fgetc函数单个读入一个字符,判断它是不是空格或者换行符或者文件末尾来分割字符串、控制行数以及输入的结束与否。在这里,我把这个单独输入的字符记为testpoint,也就是说它就像探针一样来判断输入到底进行到了哪一步。
当探针完成了它的使命之后,我们需要使用ungetc函数将探针返回到输入流中(防止出现单个字母的单词),该函数也保存在stdio.h头文件下,其声明如下:
int __cdecl ungetc(int _Ch,FILE *_File);
返回探针之后,利用fscanf遇到空格、换行符、tab停止读入的特点,将这个可疑的字符串(不知道是不是合法单词)读取进来。
之后,自定义一个函数pickaword用于剔除标点符号。实现此过程,我选择的是将\0赋值给符号位,较简单,给出代码如下:
void pickaword (char a[]){ //rid all illegal signatures
int i;
for (i = 0; a[i] != '\0' && a[i] != ',' && a[i] != '.'; i ++);
a[i] = '\0';
}
(这里默认了没有"?","!"等其他标点,如果需要可以添加上去)
接下来判断这是不是一个从来没有出现过的可疑单词。这里引用布尔变量ifanew存储是不是一个新的可疑单词,初始值为true。用for循环进行遍历,终点是结构体的个数,再用strcmp函数进行比较,如果返回值是0,表示相同的可疑单词,直接将对应的行数赋值给结构体里面的lines,同时ifanew更新为false;若返回值不是0,则使用一个新的结构体,同时结构体数量自增1。至此,我们成功读入了一个可疑单词,就是如此简单,然后执行循环,一直循环到EOF为止。
2、对结构体进行排序
排序的几种方法(简单排序法、直接插入法、冒泡法)都可以使用,这里我使用的是冒泡法,只不过这里需要判断条件是使用strcmp的返回值判断判断两个字符串的顺序,然后引用一个中间结构体tempstruct进行交换,这个问题不大。这里需要注意,在第一步中,执行完最后一个循环,结构体数量会再自增一次,所以在排序时结构体数量需要减一。放出代码:
for(int i = 0; i < structnums - 1; i ++) { //sort
for (int j = 0; j < structnums - i - 1; j ++) {
if (strcmp(words[j].word, words[j + 1].word) >= 0) {
tempstruct = words[j];
words[j] = words[j + 1];
words[j + 1] = tempstruct;
}
}
}
3、按照要求输出
这个输出我搞了好长时间,因为有太多小细节需要注意,经常弄着弄着就乱了。首先要有一个判断,只有这个可疑单词是合法的单词才能进入到输出过程,否则continue。
进去之后,首先将单词和冒号输出。如果这个单词只出现一次,那么就不用讨论是否会重复以及逗号的问题,直接输出lines[0]以及换行符。如果有两行及以上,先输出lines[0],之后再根据是否重复决定是否输出,后面的输出都采用",%d"格式输出。循环直到最后一个结构体。
完整代码献上:
struct edge {
char word[21];
int lines[201], numnow;
}words[201], tempstruct;
bool ifaword (char a[]) { //used to judge if a string is legal word
int len = strlen(a);
if (len == 0) return false;
for (int i = 0; i < len; i ++) {
if (!((a[i] >= 'a' && a[i] <= 'z') || (a[i] >= 'A' && a[i] <= 'Z') || (a[i] == '-' && i != 0))) return false;
}
if (strcmp ("a", a) == 0 || strcmp ("an", a) == 0 || strcmp ("the", a) == 0 || strcmp ("and", a) == 0) return false;
return true;
}
void pickaword (char a[]){ //rid all illegal signatures
int i;
for (i = 0; a[i] != '\0' && a[i] != ',' && a[i] != '.'; i ++);
a[i] = '\0';
}
int main() {
memset (words, 0, sizeof (words));
FILE* fpin = fopen ("crossin.txt", "r");
if (fpin == NULL) return 0;
int lines = 1, structnums = 0;
char testpoint, tempword[21]; //use a testpoint to test if is a space or the end of line or file
while ((testpoint = fgetc (fpin)) != EOF){ //if the testpoint is end of file
bool ifanew = true;
if (testpoint == '\n'){ //end of the line
lines ++;
continue;
}
else if (testpoint == ' ') continue; //a space
else{
ungetc (testpoint, fpin); //load the charactor back to the file in case one-letter-word appear
fscanf (fpin, "%s", tempword);
}
pickaword (tempword);
for (int i = 0; i < structnums; i ++) { //judge if this is a new word
if (strcmp (tempword, words[i].word) == 0) {
words[i].lines[words[i].numnow ++] = lines;
ifanew = false;
break;
}
}
if (ifanew){
strcpy (words[structnums].word, tempword);
words[structnums].lines[words[structnums].numnow] = lines;
words[structnums].numnow ++;
structnums ++;
}
}
fclose (fpin);
for(int i = 0; i < structnums - 1; i ++) { //sort
for (int j = 0; j < structnums - i - 1; j ++) {
if (strcmp(words[j].word, words[j + 1].word) >= 0) {
tempstruct = words[j];
words[j] = words[j + 1];
words[j + 1] = tempstruct;
}
}
}
FILE* fpout = fopen ("crossout.txt", "w");
if (fpout == NULL) return 0;
for (int i = 0; i < structnums; i ++) {
if (!ifaword(words[i].word)) continue;
fprintf (fpout, "%s:", words[i].word);
if (words[i].numnow == 1) fprintf (fpout, "%d\n", words[i].lines[0]);
else{
for (int j = 0; j < words[i].numnow; j ++) {
if (j == 0) fprintf (fpout, "%d", words[i].lines[0]);
else if (words[i].lines[j] != words[i].lines[j - 1]) fprintf (fpout, ",%d", words[i].lines[j]);
if (j == words[i].numnow - 1) fprintf (fpout, "\n");
}
}
}
fclose (fpout);
return 0;
}