博客 / 詳情

返回

C++統計文件內給定關鍵詞

博主剛開始學習c++,前段時間老師佈置了c++的一個作業:

給定兩個文件(一個源文件text4search.txt,一個文件keywords.txt包含需要在源文件中搜索的關鍵詞),要求輸出keywords.txt中每個關鍵詞在源文件中出現的行號。

image.png
舉個例子,如果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中的單詞,找到一個關鍵詞就輸出一個行號——這樣輸出的行號是混亂的。

基本思路

  1. 採用vector<string>儲存所有keywords;
  2. 採用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++;
    }

大功告成!

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.