文本查询
直接看参考答案版
即可
题目
文本查询
该程序将读取用户指定的任意文本文件【当前目录下的china_daily.txt
】,然后允许用户从该文件中查找单词。
查询的结果是该单词出现的次数,并列出每次出现所在的行。
如果某单词在同一行中多次出现,程序将只显示该行一次。行号按升序显示。
要求:
a. 它必须允许用户指明要处理的文件名字。
b. 程序将存储该文件的内容,以便输出每个单词所在的原始行。
vector<string> lines
;//O(1)
c. 它必须将每一行分解为各个单词,并记录每个单词所在的所有行。
在输出行号时,应保证以升序输出,并且不重复。
map<string, set<int> > wordNumbers;
map<string, int> dict;
d. 对特定单词的查询将返回出现该单词的所有行的行号。
e. 输出某单词所在的行文本时,程序必须能根据给定的行号从输入
文件中获取相应的行。
示例:
使用提供的文件内容,然后查找单词 “element”。输出的前几行为:
---------------------------------------------
element occurs 125 times.
(line 62) element with a given key.
(line 64) second element with the same key.
(line 153) element |==| operator.
(line 250) the element type.
(line 398) corresponding element.
---------------------------------------------
//程序接口[可选]:
class TextQuery
{
public:
//......
void readFile(const string & filename);
void query(const string & word);//
private:
//......
vector<string> _lines;//O(1)
map<string, set<int> > _wordNumbers;//the the
map<string, int> _dict;//
};
//程序测试用例
int main(int argc, char *argv[])
{
string queryWord("hello");
TextQuery tq;
tq.readFile("test.dat");
tq.query(queryWord);
return 0;
}
思路
这个程序的功能是读取指定的文本文件并允许用户查询某个单词的出现次数和所在行。主要的数据结构和方法如下:
类 TextQuery
void readFile(const string & filename)
读取用户指定的文本文件,并将文件的内容按行存储在vector<string> _lines
中。每一行的内容会拆分为单词,记录每个单词在哪些行中出现过,并存储在map<string, set<int>> _wordNumbers
中。void query(const string & word)
允许用户查询某个单词,返回该单词出现的次数及其所在的行号。行号会以升序输出,并且每个单词所在的行只会出现一次,即使该单词在同一行多次出现。数据结构
vector<string> _lines
这是一个存储文本文件每一行的向量。通过它,程序可以在输出行号时方便地检索对应的行文本。map<string, set<int>> _wordNumbers
这是一个映射结构,其中键为单词,值为一个整型集合,表示该单词出现在文件中的哪些行。集合set<int>
确保行号不重复且自动升序排列。map<string, int> _dict
这是一个记录每个单词在文件中总共出现次数的字典,键为单词,值为出现的次数。用于输出查询结果时显示该单词的总出现次数。流程
- 文件读取:
用户指定文件名,readFile()
函数读取文件内容,将每一行存储到_lines
中。然后,逐行拆分每一行中的单词,并将每个单词的行号记录到wordNumbers
中,同时更新dict
以记录每个单词的出现次数。- 单词查询:
用户输入想要查询的单词,query()
函数从wordNumbers
中查找该单词,并输出该单词所在的所有行号及行文本。每个单词的行号以升序输出,并且不会重复。示例输出假设用户查询单词
element
,查询结果显示该单词共出现125次,并在特定的行号中出现(如第62行、第64行等),且输出这些行的具体文本。
参考答案版
#include <iostream>
#include <string>
#include <fstream>
#include <sstream>
#include <vector>
#include <set>
#include <map>
using std::cout;
using std::endl;
using std::cerr;
using std::string;
using std::ifstream;
using std::istringstream;
using std::vector;
using std::set;
using std::map;
class TextQuery
{
public:
//构造函数--初始化类 TextQuery 存储行的容量capacity
TextQuery(int capacity){
_lines.reserve(capacity); //为存储每一行内容预留空间 reserve()函数
}
//读取第一步:读取文件到ifstream(ifs)对象,它的数据源是一个文件
void readFile(const string & fileName){
ifstream ifs(fileName); //读取文件内容
if(!ifs){
cerr << "ifstream open file failed" << endl;
return;
}
//接下来关于读取的核心2个步骤:
//1-std::getline(ifs, line) 从文件输入流 ifs 中逐行读取内容,读取的数据存储在 line 这个字符串变量中。
//2-istringstream iss(line) 将 line 这个字符串放入流中,接着使用 iss >> word 从字符串流中逐个提取单词。
//读取第二步:按行处理读取到的文件内容
string line; //字符串,用来存储文件每一行内容
size_t lineNumber = 1; //行号,从1开始。因此需要注意与下标(从0开始)的关系
while(std::getline(ifs,line))
{
_lines.push_back(line); //利用vector记录文本每一行的内容
dealLine(line); //处理每一行内容
//读取第三步:istringstream(iss)用于从字符串中提取数据,它的数据源是一个字符串
istringstream iss(line); //创建一个iss对象,用line字符串初始化
string word;
while(iss >> word) //从iss流中提取下一个以空格或其他空白符为分隔的单词,并将其存储到 `word` 中。
{
//统计单词和其出现的次数
++_dict[word];
//统计单词出现的行号
_wordNumbers[word].insert(lineNumber); //将行号放入map容器的元素<string,set<int>> set容器中
}
++lineNumber; //处理完一行,继续下一行
}
ifs.close(); //关闭文件
}
void dealLine(string & line){
for(auto & ch: line) //ch是字符串(std::string&类型)的每一个字符,auto是char类型(准确来说是char&)
{
//如果该字符是是符号或数字就替换成空格
if(!isalpha(ch)){
ch=' ';
}
else if(isupper(ch)){ //说明是字母,则考虑大小写问题(比如同一个单词出现在句首,出现在句中。实际上都是一个单词)
//如果是大写字母,就把它改为小写字母
ch = tolower(ch);
}
}
}
//查找单词
void query(const string & word){
cout << "----------------" << endl;
//根据示例,首先打印单词出现的总次数
int count = _dict[word]; //count默认值为0,因此后续在输出
cout << word << " occurs " << count << (count>1 ? " times " : " time ") << endl; //输出合适的语法(复数,单数)
//单词出现1次用time,出现2次及以上用times
//根据示例,再打印单词出现的行号及内容
set<int> lineNums = _wordNumbers[word]; //lineNums是set容器,存储的是该单词出现的所有行号
for(auto & num: lineNums)
{
cout << "(" << "line " << num << ")" << _lines[num-1] << endl;
//vector容器可以按照下标访问
//vector[index]存储的就是每一行文本内容,string类型
}
}
private:
//vector 记录每一行的内容,元素类型为string
vector<string> _lines;
//map 记录查找的单词 和 该单词出现的所有行,元素类型为<string , set<int>>
map<string, set<int> > _wordNumbers;
//map 统计单词 和 该单词的词频, 元素类型为<string , int>
map<string, int> _dict;
};
//程序测试用例 -- 测试查找 Chinese 和 the
int main(){
TextQuery tq(128); //使用了构造函数初始化TextQuery类型的对象tq,分配可以存储128行的空间
//注意不是一行存储128个字符,每行字符数没有限制
tq.readFile("china_daily.txt");
//string queryWord("chinese"); //假设查找的单词是Chinese,我们输入的要全是小写,因为文本内容读入时已经全部转化为小写了
string queryWord("the");
tq.query(queryWord);
return 0;
}
注意点
1-初始化类
TextQuery(int capacity){
_lines.reserve(capacity);
}
//……
TextQuery tq(128);
初始化类
TextQuery
的一个对象tq
,并且使用了构造函数来为该对象进行初始化。具体解释:
- 类对象初始化:
TextQuery
是一个类,当你创建对象时(例如TextQuery tq(128);
),会调用类的构造函数对对象进行初始化。
- 构造函数:
构造函数是类中的一个特殊成员函数,在创建对象时自动被调用,用来初始化对象的成员变量。
你在
TextQuery
类中定义了如下的构造函数:TextQuery(int capacity){ _lines.reserve(capacity); }
这个构造函数有一个整型参数
capacity
,表示容量。它的作用是为私有成员变量_lines
(一个vector<string>
类型)预留一定数量的空间,以提高性能,避免动态扩容。
TextQuery tq(128)
:
- 这里你创建了一个
TextQuery
类的对象tq
,并传递了参数128
这会调用构造函数TextQuery(int capa)
,给_lines
这个vector
分配初始空间,容量为 128。因此,这一步是初始化类的对象,主要用来为
_lines
预留空间,从而提升后续操作的效率。为什么要预留空间?
_lines
是一个vector<string>
,它存储的是文本文件中每一行的原始信息。由于文件可能会有很多行,预先知道大致行数的情况下,通过reserve()
预留空间,可以减少动态扩展vector
时内存的重新分配和拷贝操作,提升程序性能。预留128行,而不是为一行预留128个字符
_lines.reserve(capacity);
de
_lines
是一个vector<string>
,用来存储文件中的每一行文本(string
类型的对象)。
reserve(capacity)
是vector
的方法,用来预留空间,确保vector
容量至少为capacity
个元素。在这个例子中,capacity
的值是128
,这意味着_lines
预留了可以存储128 行文本的空间。与字符数量无关
reserve(107)
并不是指每一行有 128 个字符,而是指_lines
这个vector
能够存储 128 个string
对象,也就是最多能存储 128 行文本。
- 每个
string
对象的大小是可变的,它能够根据实际的文本行的长度动态扩展。reserve
只是预先分配内存以避免在添加行时频繁地重新分配内存,提升性能。它不会限制每行的字符数。
2-关于ifs
和 iss
在代码中的区别 及 作用
ifs
和iss
都有“输入”的作用,但它们的作用和使用场景有所不同:
ifs
(ifstream):
ifs
是一个输入文件流对象(std::ifstream
),用于从文件中逐行读取内容。在这段代码中,std::getline(ifs, line)
表示从ifs
(文件)中读取一行内容,存储到字符串变量line
中。ifs
负责从外部文件中读取数据,将整个文件内容逐行读入程序。
iss
(istringstream):
iss
是一个输入字符串流对象(std::istringstream
),它的作用是将字符串(这里是从文件中读取的一行line
)作为输入流处理。在这段代码中,istringstream iss(line)
将字符串line
放入到iss
中,然后通过iss >> word
从字符串流中逐个读取单词。iss
的作用是将一行文本转换为流,以便可以逐个提取单词。总结
ifs
是用来从文件中读取数据的流,处理的是文件的整体内容,逐行读取。iss
是用来处理每一行字符串的流,逐个提取单词,便于进一步统计和处理。因此,两者虽然都是输入流,但
ifs
处理的是文件输入,iss
处理的是字符串输入。
3-ifs
检验文件是否正确读入
if(!ifs) {
cerr << "ifstream open file failed" << endl;
return;
}
在C++中,
ifstream
(输入文件流)对象可以用于检查文件是否成功打开或是否处于良好的状态。ifs
的返回值实际上是它的状态,即它包含的信息用来判断文件流是否成功打开并可以正常读取数据。
ifs
是一个std::ifstream
对象。当使用if(!ifs)
时,实际上是在检查这个文件流对象的状态。
ifstream
对象可以隐式转换为bool
类型,以检查流的状态。这个布尔值用来表示流是否处于有效状态(即流是否成功打开、是否可读等)。
- 如果文件流打开成功且可以正常操作,则转换为
true
。- 如果文件流打开失败或者遇到错误,则转换为
false
。
!ifs
检查的是文件流是否失败,常用于判断文件是否成功打开或读取时是否出错。如果文件打开失败,则!ifs
为true
,程序执行cerr << "ifstream open file failed" << endl;
输出错误信息。
4-关于iss
2种情况的区别和作用
//istringstream iss(line);
//while(iss >> word);
istringstream iss(line);
和while(iss >> word)
是两个不同的操作,虽然它们都与字符串输入流iss
有关,但它们的功能和作用有所区别。istringstream iss(line);
- 这行代码的作用是创建一个
istringstream
对象iss
,并用字符串line
来初始化它。istringstream
是 C++ 标准库中的字符串输入流类,它可以将一个字符串当作输入流,类似于如何从文件或键盘读取输入。iss(line)
将line
字符串“装载”到流中,准备让接下来的操作从这个字符串中提取数据。也就是说,iss
在这个时候是从line
字符串中读取内容。while(iss >> word);
- 这行代码开始对
iss
中的数据进行逐步读取。iss >> word
代表从iss
流中提取下一个以空格或其他空白符为分隔的单词,并将其存储到word
中。while(iss >> word)
会在流还能读取到单词时持续执行(即当流没有到达末尾时)。每次读取一个单词,iss
流的状态会更新,直到无法再提取出新的单词时循环结束。区别和作用
istringstream iss(line);
:- 作用:创建并初始化字符串输入流对象
iss
,将line
字符串作为流的数据源。- 这是流的初始化过程,将字符串包装成流。
while(iss >> word)
:- 作用:从
iss
流中逐个提取单词,直到流中没有更多的单词可供读取。提取出的每个单词存储在word
变量中。- 这是从流中提取数据的过程,通过操作符
>>
将流中的内容分词读取。总结
istringstream iss(line)
:初始化字符串流,用字符串line
创建流对象。while(iss >> word)
:从这个流中逐个提取单词,直到流中没有更多内容。两者的关系是:
istringstream iss(line)
首先将字符串转换为输入流,接着while(iss >> word)
负责从这个流中读取数据。
5-处理每一行文本内容:关于大小写、符号或者数字 的问题
void dealLine(string & line){
for(auto & ch: line) //ch是字符串(std::string&类型)的每一个字符,auto是char类型(准确来说是char&)
{
//如果该字符是是符号或数字就替换成空格
if(!isalpha(ch)){
ch=' ';
}
else //说明是字母,则考虑大小写问题(比如同一个单词出现在句首,出现在句中。实际上都是一个单词)
{
if(isupper(ch)){
//如果是大写字母,就把它改为小写字母
ch = tolower(ch);
}
}
}
}
这段代码是一个处理字符串的函数
dealLine
,它的主要作用是遍历字符串中的每一个字符,将非字母字符替换成空格,并将大写字母转换为小写字母。逐步分析:
for(auto & ch : line)
:
- 这是一个范围
for
循环,auto & ch
表示每次循环时,ch
是字符串line
中的一个字符的引用。- 使用
auto
自动推断出ch
的类型。由于line
是一个std::string
类型,ch
的实际类型是char&
,即字符的引用。
if(!isalpha(ch))
:
isalpha(ch)
是一个标准库函数,检查ch
是否是字母字符(包括大写和小写字母)。如果ch
不是字母(如数字或符号),isalpha(ch)
返回false
,因此条件!isalpha(ch)
为true
。- 当
ch
不是字母时,代码将该字符替换为空格ch = ' '
。
else if(isupper(ch))
:
isupper(ch)
用来检查ch
是否是大写字母。如果ch
是大写字母,条件为真,接下来调用tolower(ch)
,将大写字母转换为小写字母,并赋值回ch
。tolower(ch)
是一个标准库函数,用来将大写字母转换为小写字母。总结这个
dealLine
函数的功能是:
- 对输入的字符串
line
进行遍历处理。- 将所有非字母字符(如标点符号和数字)替换为空格。
- 将所有大写字母转换为小写字母。
这样处理的结果是字符串
line
会变成只包含小写字母和空格的形式,方便后续的单词提取和统计工作。
6-输出词频times 还是 time
cout << word << " occurs " << count << (count>1 ? " times " : " time ") << endl;
判断 count > 1
是为了选择适当的语法表述。
这段代码的逻辑是:当一个单词只出现一次时
- 如果
count
大于 1,就输出"times"
以表示复数。 - 否则,输出
"time"
以表示单数。
ai代码存档
第一版
#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include <map>
#include <set>
#include <string>
using namespace std;
class TextQuery {
public:
// 读取文件内容并处理
void readFile(const string &filename);
// 查询某个单词
void query(const string &word);
private:
vector<string> _lines; // 存储文件的每一行
map<string, set<int>> _wordNumbers; // 记录每个单词在哪些行中出现
map<string, int> _dict; // 记录每个单词的总出现次数
};
// 读取文件并处理
void TextQuery::readFile(const string &filename) {
ifstream file(filename); // 打开文件
if (!file) {
cerr << "File could not be opened!" << endl;
return;
}
string line;
int lineNumber = 0; // 行号从0开始
while (getline(file, line)) { // 逐行读取文件
_lines.push_back(line); // 将每一行存储到_lines中
lineNumber++; // 行号递增
// 使用 istringstream 分解每一行
istringstream lineStream(line);
string word;
while (lineStream >> word) { // 逐个读取单词
_wordNumbers[word].insert(lineNumber); // 记录单词出现的行号,set确保唯一
_dict[word]++; // 记录单词总出现次数
}
}
file.close(); // 关闭文件
}
// 查询某个单词
void TextQuery::query(const string &word) {
// 检查单词是否存在
if (_dict.find(word) == _dict.end()) {
cout << word << " does not appear in the text." << endl;
return;
}
// 输出单词出现的次数
cout << word << " occurs " << _dict[word] << " times." << endl;
// 输出单词所在的行号及其对应的行文本
for (int lineNumber : _wordNumbers[word]) {
cout << "(line " << lineNumber << ") " << _lines[lineNumber - 1] << endl;
}
}
int main(int argc, char *argv[]) {
string queryWord = "element"; // 查询的单词
TextQuery tq;
tq.readFile("china_daily.txt"); // 指定文件名读取
tq.query(queryWord); // 查询单词
return 0;
}
第二版
#include <iostream> #include <fstream> #include <sstream> #include <vector> #include <map> #include <set> #include <string> using namespace std; class TextQuery { public: // 读取文件内容并处理 void readFile(const string &filename); // 查询某个单词 void query(const string &word); // 获取指定行号的内容 string getLineContent(int lineNumber) const; private: vector<string> _lines; // 存储文件的每一行 map<string, set<int>> _wordNumbers; // 记录每个单词在哪些行中出现 map<string, int> _dict; // 记录每个单词的总出现次数 }; // 读取文件并处理 void TextQuery::readFile(const string &filename) { ifstream file(filename); // 打开文件 if (!file) { cerr << "File could not be opened!" << endl; return; } string line; int lineNumber = 0; // 行号从0开始 while (getline(file, line)) { // 逐行读取文件 _lines.push_back(line); // 将每一行存储到_lines中 lineNumber++; // 行号递增 // 使用 istringstream 分解每一行 istringstream lineStream(line); string word; while (lineStream >> word) { // 逐个读取单词 _wordNumbers[word].insert(lineNumber); // 记录单词出现的行号,set确保唯一 _dict[word]++; // 记录单词总出现次数 } } file.close(); // 关闭文件 } // 获取指定行号的内容 string TextQuery::getLineContent(int lineNumber) const { if (lineNumber <= 0 || lineNumber > _lines.size()) { return "Invalid line number!"; } return _lines[lineNumber - 1]; // 返回对应行的内容(行号从1开始) } // 查询某个单词 void TextQuery::query(const string &word) { // 检查单词是否存在 if (_dict.find(word) == _dict.end()) { cout << word << " does not appear in the text." << endl; return; } // 输出单词出现的次数 cout << "---------------------------------------------" << endl; cout << word << " occurs " << _dict[word] << " times." << endl; // 输出单词所在的行号及其对应的行文本 for (int lineNumber : _wordNumbers[word]) { cout << "(line " << lineNumber << ") " << _lines[lineNumber - 1] << endl; } cout << "---------------------------------------------" << endl; // 提示用户输入行号以获取详细行内容 int inputLineNumber; cout << "Enter line numbers to view the content (enter 0 to exit):" << endl; while (true) { cout << "> "; // 显示提示符 cin >> inputLineNumber; // 获取用户输入的行号 if (inputLineNumber == 0) { cout << "Exiting..." << endl; break; // 输入0时退出循环 } // 获取并输出对应行内容 cout << "Content of line " << inputLineNumber << ": " << endl; cout << getLineContent(inputLineNumber) << endl; } } int main(int argc, char *argv[]) { string queryWord; // 查询的单词 cout << "input your word: "; cin >> queryWord; TextQuery tq; tq.readFile("china_daily.txt"); // 指定文件名读取 tq.query(queryWord); // 查询单词 return 0; }