索引生成器项目拼写检查项目

处理字典的另一种策略

在第一个版本的拼写检查程序中,我们通过读取字典文件中的赢博体育字符串将单词字典加载到内存中。我们构造了一个指向字符串的指针数组来存储赢博体育这些单词,然后在该数组中实现了二进制搜索。

在这个版本的拼写检查程序中,我们将采用稍微不同的策略。这一次,我们将把字典中的单词保留在原始文件中。为了更容易地定位文件中的单词,我们将构建一个索引,它可以告诉我们文本文件中每个单词的位置。当拼写检查器启动时,只有索引将被加载到内存中,并且我们将使用索引来定位我们想要在文本文件中查找的单词,并且当我们进行搜索时,每次只从文本文件中读取一个单词。

创建索引

设置第二个版本的拼写检查器的第一步是构造索引。索引将是一个整数数组,索引中的每个条目将在字典文件中存储一个字典单词的位置。

今天我们要看的第一段代码是一个程序index.c,它构造索引并将索引存储在一个文件中。这将节省我们以后的工作,因为拼写检查程序将能够在开始时从文件加载索引,而不必为每次运行拼写检查程序重新构建索引。

下面是index.c的代码:

#include <stdio.h> #include <stdlib.h> int main() {FILE* f1 = fopen("words.txt","r");int wordCount = 1;char ch;While (! fof (f1)) {ch = fgetc(f1);if(ch == '\n') wordCount++;} FILE* f2 = fopen("index.idx","wb");写入文件(wordcount sizeof (int), 1, f2);int* positions = (int*) malloc((wordCount+1)*sizeof(int));倒带(f1);wordCount = 0;positions[wordCount] = (int) ftell(f1);While (! fof (f1)) {ch = fgetc(f1);if(ch == '\n') {wordCount++;positions[wordCount] = (int) ftell(f1);}} wordcount++;positions[wordCount] = (int) ftell(f1) + 1;文件关闭(f1);写入文件(位置,sizeof (int), wordCount + 1, f2);文件关闭(f2);返回0;}

main()中的第一个循环计算words.txt文件中有多少个单词。这将告诉我们索引数组应该有多大。

索引数组将包含words.txt文件中每个单词的第一个字符在文件中的位置。我们还将在末尾添加一个额外的条目,该条目告诉我们如果文件中还有一个单词,下一个单词将从哪里开始。

为了存储索引,我们将设置一个名为index.idx的文件。该文件将是一个二进制文件,这将允许我们以二进制形式直接将索引数组写入文件。这将最终节省拼写检查程序的时间,因为我们不必将索引列表从文本转换为整数数组:我们可以简单地直接从二进制索引读取整数。idx文件。

为了使用二进制文件,我们在打开文件进行写入时做了一个小的更改。下面是我们用来打开文件的命令:

FILE* f2 = fopen("index.idx","wb");

fopen()调用中的第二个参数是文件模式:在这种情况下,我们使用‘w’表示我们正在打开文件进行写入,‘b’表示我们将在二进制模式下使用该文件。

要将数据写入二进制文件,我们将使用fwrite()函数。我们要写入文件的第一件事是索引的大小:

写入文件(wordcount sizeof (int), 1, f2);

fwrite()函数接受四个参数。第一个参数是指向存储数据的内存位置的指针。在本例中,我们编写的是存储在变量wordCount中的单个整数,因此我们提供该整数变量的地址作为第一个参数。第二个参数表示我们要写入的项的字节大小,第三个参数表示我们要写入文件的项的数量。第四个参数是文件指针。

index.c中的其余代码构造索引数组。为此,我们将扫描word .txt文件,查找换行符。当我们看到一个换行符时,我们知道我们已经到达了一个单词的末尾,下一个来自文件的字符是一个新单词的开头。此时,我们可以使用ftell()函数来获取文件中下一个字符的位置。我们将这个位置信息存储在索引数组中。

一旦构造了索引数组,最后调用fwrite()将整个位置信息数组写入文件。

新的拼写检查器

在版本二的拼写检查程序中,当拼写检查程序启动时,我们将只加载索引数组到内存中。字典中的单词本身将保存在words.txt文件中,我们只在需要时才会阅读这些单词。

考虑到这种稍微不同的安排,我们需要对字典结构体和初始化它的代码做一些小的修改:

typedef struct {int N;//字典中的单词数int* index;文件*单词;}字典;dictionary* loadDictionary(const char* indexName,const char* dictName) {FILE* f = fopen(indexName,"rb");Dictionary * d = (Dictionary *) malloc(sizeof(Dictionary));从文件中读(& (d - > N), sizeof (int), 1, f);d - >指数= (int *) malloc ((d - > N + 1) * sizeof (int));从文件中读(d - >索引,sizeof (int), d - > N + 1, f);文件关闭(f);d->words = fopen(dictName,"r");返回d;}

字典结构现在将包含一个指向索引数组的指针,索引数组是一个大小为N+1的整型数组,以及一个指向word .txt文件的文件指针。

loadDictionary()函数将以二进制模式打开索引文件以供读取,然后从文件中读取字数,然后读取整个索引数组。要执行读取操作,我们将使用fread()函数。和fwrite()一样,这个函数有四个参数。第一个参数是一个指针,指向我们想要存储数据的内存位置。第二个和第三个参数指定我们想要读取的每个单元的字节大小以及我们想要读取多少个单元。第四个参数是文件指针。

下面是修改后的searchDictionary()函数。这一次最大的不同是,字典中的单词没有存储在内存中。为了在字典列表中进行二进制搜索,我们必须在二进制搜索代码需要的时候从文件中读取一个单词。

int searchDictionary(dictionary* d,const char* word) {int start = 0;int end = d->N-1;char fileWord [64];While (start <= end) {int mid = (start+end)/2;fseek (d - >话说,d - >索引(中期),SEEK_SET);size_t bytesRead = fread(fileWord,sizeof(char),d->索引[mid+1]-d->索引[mid]-1,d->字);fileWord[bytesRead] = '\0';int comp = strcmp(word,fileWord);If (comp == 0) return mid;if(comp < 0) end = mid -1;Else start = mid + 1;}返回-1;}

要从字典文件中读取一个单词,我们首先使用fseek()函数来设置文件在文件中的位置。fseek()函数允许我们指定从文件开头开始设置文件位置的字节数。在这段代码中,我们想要从文件中读取索引为mid的单词,所以我们使用d->index[mid]来查找该单词在索引中的起始位置。

然后,我们发出fread()命令,将该单词读入一个字符数组,该字符数组设置为接收该单词。readad()的第三个参数需要设置为我们想要读取的字节数。我们可以通过计算这个单词的位置差和它后面的单词的位置差来计算字节数。Fread()将在不使用‘\0’结尾字符的情况下读取单词。我们必须自己插入那个字符来完成这个单词。

这个版本的拼写检查程序将进行更多的文件交互,因为我们在字典中搜索的每个单词都将从文件中产生多次读取。赢博体育这些读取确实减慢了拼写检查器的速度,但赢博体育这些读取对性能的影响并没有那么严重。第二个版本的拼写检查器仍然可以在合理的时间内对中等长度的文件进行拼写检查。

编程任务

在这个作业中,我们将构造第三个版本的拼写检查程序。在这个版本的程序中,我们将在拼写检查器启动时将索引文件和完整的单词列表加载到内存中。

在本作业中,您还将练习使用另一组用于从文件中读取数据的函数。这些功能在教科书的第二章中有描述。首先阅读第二章中的open()、read()和close()函数。您将使用这些函数来代替fopen()、fread()和fclose()进行此赋值。

首先修改字典结构体的结构声明:

typedef struct {int N;//字典中的单词数int* index;char *的话;}字典;

该结构体现在将包含指向两个大数组的指针。索引指针将指向索引数组,而单词指针将指向一个数组,该数组将字典文件中的赢博体育文本包含在一个巨大的数组中。

修改loadDictionary()函数以从索引文件和words文件中读取索引数组和words数组。读取words文件时,首先使用malloc()构造一个大小比words文件大1的字符数组。要确定字典文件的字节大小,您应该使用stat()函数:这里有一些示例代码演示如何做到这一点。

在将字典文件的内容读入数组后,将数组中的最后一个位置设置为‘\0’,然后在数组中运行一个循环,将每个‘\n’字符转换为‘\0’。

由于字典结构已经改变,您还必须适当地修改freeDictionary()。

最后,您必须修改searchDictionary()函数,以便在单词数组中查找单词,而不是从单词文件中读取单词。

有了这些更改,拼写检查器现在应该运行得更快了。