博主刚开始学习c++,前段时间老师布置了c++的一个作业:
给定两个文件(一个源文件text4search.txt,一个文件keywords.txt包含需要在源文件中搜索的关键词),要求输出keywords.txt中每个关键词在源文件中出现的行号。
举个例子,如果keywords.txt中有一个关键词是c++
,在text4search.txt中第1,7,9,43,543,586,2445行都出现了c++
,那么应该输出c++:{1 7 9 43 543 586 2445 }
。
先上完整版代码(代码已在vscode c++11标准运行成功)
#include <algorithm>
#include <fstream>
#include <iostream>
#include <map>
#include <sstream>
#include <vector>
using namespace std;
// 报错函数
void error(const char *p, const char *p2 = "") {
std::cerr << p << ' ' << p2 << std::endl;
std::exit(1);
}
map<string, vector<int>> keyCount(ifstream &inputFile, vector<string> &keywords) {
map<string, vector<int>> resultMap;
string line;
int lineNum = 0;
while (getline(inputFile, line)) {
++lineNum;
// 读入文件的每一行
std::istringstream sin(line);
string word;
//每行单词逐个检查是不是在keywords里面出现,而不是统计keyword在一行中出现的次数
//这样不用单独写一个函数统计一行中某个关键词出现次数,也能够出现一次关键词就压入行号一次
while (sin >> word) {
auto it = find(keywords.begin(), keywords.end(), word);
if (it != keywords.end()) {
resultMap[word].push_back(lineNum);
}
}
}
return resultMap;
}
int main() {
// 建立三个文件流对象并关联相应文件,注意这里大家要修改为自己的文件路径(或者使用命令行输入)
std::ifstream fin1;
fin1.open("D:/University/Code/cpp_vscode/lab4/keywords.txt"); // 和keywords.txt建立关联
if (!fin1) {
error("cannot open input file", "D:/University/Code/cpp_vscode/lab4/keywords.txt");
}
std::ifstream fin2;
fin2.open("D:/University/Code/cpp_vscode/lab4/text2search.txt"); // 和text2search.txt建立关联
if (!fin2) {
error("cannot open input file", "D:/University/Code/cpp_vscode/lab4/text2search.txt");
}
std::ofstream fout;
fout.open("D:/University/Code/cpp_vscode/lab4/result.txt"); // 和result.txt建立关联
if (!fout) {
error("cannot open output file", "D:/University/Code/cpp_vscode/lab4/result.txt");
}
// 将keywords存入keys这个字符串向量中(用fin1)
vector<string> keys;
string s;
while (fin1 >> s) {
keys.push_back(s);
}
fin1.close();
// 利用fin2和keys产生结果map
map<string, vector<int>> result = keyCount(fin2, keys);
fin2.close();
//输出,由于map会自动按照字典序排序,而行号也是按照升序逐行检测,因此输出不用再排序
map<string, vector<int>>::iterator it1 = result.begin();
while (it1 != result.end()) {
fout << it1->first << " : " << "{";
//it1->second是vector<int>类型,不能直接用fout输出
for (const auto &it2 : it1->second) {
fout << it2 << ",";
}
fout << "}" << endl;
it1++;
}
fout.close();
system("pause");
return 0;
}
下面结合代码讲一讲我的思路。
这道题的难点在于如何选取正确、高效的存储方法和搜索方法:
显然我们不能简单的挨个读取text中的单词,找到一个关键词就输出一个行号——这样输出的行号是混乱的。
基本思路
- 采用
vector<string>
储存所有keywords; - 采用
map<string,vector<int>>
储存每个关键词到所出现行号的映射(由于关键词出现的行号有多个,因此采用vector<int>
储存行号组成的整数型向量)
具体代码
首先,这里涉及到文件的输入输出,肯定是要用到<fstream>
的,然后自定义文件输入输出流对象并链接到对应的文件:
// 建立三个文件流对象并关联相应文件,注意这里大家要修改为自己的文件路径(或者使用命令行输入)
std::ifstream fin1;
fin1.open("D:/University/Code/cpp_vscode/lab4/keywords.txt"); // 和keywords.txt建立关联
if (!fin1) {
error("cannot open input file", "D:/University/Code/cpp_vscode/lab4/keywords.txt");
}
std::ifstream fin2;
fin2.open("D:/University/Code/cpp_vscode/lab4/text2search.txt"); // 和text2search.txt建立关联
if (!fin2) {
error("cannot open input file", "D:/University/Code/cpp_vscode/lab4/text2search.txt");
}
std::ofstream fout;
fout.open("D:/University/Code/cpp_vscode/lab4/result.txt"); // 和result.txt建立关联
if (!fout) {
error("cannot open output file", "D:/University/Code/cpp_vscode/lab4/result.txt");
}
用文件输入流对象fin每次读入text中的一行,再用字符串输入流对象sin逐个读入该行的单词:
int lineNum = 0;
//读入文件的每一行
while (getline(inputFile, line)) {
++lineNum;
std::istringstream sin(line);//新建sin准备读入每行中的单词
接下来,我们的关键点在于,如何用比较好的方法搜索并储存好每个关键词出现的行号呢?
一般而言,有两种搜索思路:
- 每次读取一个keyword,在text全文中搜索这个keyword在哪些行出现了,记录行号;
每次读取text中的一行,再逐个读取这一行中的每个词,最后搜索这个词是不是出现在keywords.txt中,如果出现,则记录该行的行号;
那么,哪一种搜索方法更好更高效呢?显然是第二种。第一种方法思路简单,但每次搜索全文的代价太高。以下是我采用第二种搜索的思路的代码:
//sin读入每行中的每个单词
while (sin >> word) {
//使用find函数和迭代器搜索每个sin读入的单词是否出现在keywords这个vector中
auto it = find(keywords.begin(), keywords.end(), word);
if (it != keywords.end()) {
//如果找到了,那么将这个关键词对应的行号存入map中
resultMap[word].push_back(lineNum);
}
走到这里,已经基本成功啦!最后只需要把我们的答案正确输出,这里我使用迭代器it1
输出map中的关键词,由于map中的value值实际是vector类型,不能简单使用it1->second
输出,因此我们再定义一个it2
输出vector<int>
中的内容
map<string, vector<int>>::iterator it1 = result.begin();
while (it1 != result.end()) {
fout << it1->first << " : " << "{";
//it1->second是vector<int>类型,不能直接用fout输出
for (const auto &it2 : it1->second) {
fout << it2 << ",";
}
fout << "}" << endl;
it1++;
}
大功告成!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。