第十四篇:一個文字查詢程式的實現

穆晨發表於2017-01-26

前言

       本文將講解一個經典的文字查詢程式,對前面所學的容器相關知識進行一個從理論到實際的昇華,同時也對即將學習的物件導向知識來一次初體驗。

程式描述

       要求實現這樣一個程式:讀取使用者指定的檔案,然後允許使用者從中查詢某個單詞所在的位置。

一個程式導向的落後的設計思想

  將待檢索檔案以行為單位存放到Vector容器中,然後遍歷容器,將容器內元素依次轉存到字串流物件中,然後在內層遍歷這個字串流物件,檢索是否存在與給定單詞匹配的單詞。如果有則輸出該行內容以及該行序號。

落後的原因及先進的設計思想

       這是我以前嘗試解決這個問題的思路。其本質是一個典型的程式導向思想,只不過用容器簡化了些操作罷了。

       這種思路使得每次執行檢索都要重新遍歷一次檔案。如果當檔案比較大的時候,這樣的檢索效率是不能為使用者所接受的。

       最好的方法應該是採用物件導向的方法,設定一個類,該類封裝一個資料結構專門記載關於單詞與行號的資訊,該類還同時封裝初始化函式,查詢函式等功能函式。

       另外,如果我這裡不設定一個類,而是直接全域性定義一個資料結構來記載關於單詞與行號的資訊,那麼當還要對文字實現一些其他功能的時候,程式將會變得雜亂無章,程式碼裡到處都是亂七八糟的資料結構和全域性變數。這就是類封裝性的好處,也是物件導向的美妙之處之一。

下面,將用物件導向的思想“美妙”地設計出這個文字查詢程式... ...

第一步:設計類

       第1步:確定類所包含的方法

       1. read_file 函式:將指定的待檢索檔案存入容器並初始化”單詞-行號“資料結構

       2. run_query 函式:獲取待查詢單詞並返回單詞在文字中的行號

       3. text_line 函式:獲取某個行號,返回檔案中該行的內容。

       第2步:確定類所包含的資料

       1. 一個string物件存放要查詢的單詞

       2. 一個vector容器存放待檢索的文字

       3. 一個map容器存放單詞和它對應的行號

       類定義如下():

 1 class TextQuery {
 2 public:
 3     // 為行號型別取個別名( 行號的型別實在是太長了 )
 4     typedef std::vector<std::string>::size_type line_no;
 5     // 將資料存入vector容器並初始化單詞 - 行號資料結構
 6     void read_file(std::ifstream &is) {
 7         // 將待檢索檔案存入容器
 8         store_file(is);
 9         // 建立單詞 - 行號資料結構
10         build_map();
11     }
12     // 根據使用者指定的單詞執行查詢並返回結果行號( 結果是放在一個set容器中的 )。
13     std::set<line_no> run_query(const std::string&) const;
14     // 根據行號返回該行內容
15     std::string text_line(line_no) const;
16 private:
17     // 下面這兩個函式是上面read_file 函式的實現函式,是內部函式因此設為私有。
18     void store_file(std::ifstream&);    // 將資料存入vector容器
19     void build_map();    // 初始化單詞 - 行號資料結構
20     // 一個vector容器
21     std::vector<std::string> lines_of_text;
22     // 一個單詞 - 行號資料結構
23     std::map< std::string, std::set<line_no> > word_map;    // 注意這個是容器的容器 因此尖括號後面要留空格
24 };

       如此一來,整個程式的框架就顯得豁然開朗。可見設計類這一環節的重要性。事實上,工程中常用UML之類的技術專門處理這個環節。

第二步:實現類

       1. 實現store_file 函式:

1 void TextQuery::store_file(ifstream & is) {
2     string textline;
3     while (getline(is, textline))
4         lines_of_text.push_back(textline);
5 }

       2. 實現bulid_map 函式:

1 void TextQuery::build_map()
2 {
3     for (line_no line_num = 0; line_num != lines_of_text.size()) {
4         istringstream line(lines_of_text[line_num]);
5         string word;
6         while (line >> word)
7             word_map[word].insert(line_num);
8     }
9 }

       3. 實現run_query 函式:

1 set<TextQuery::line_no>
2 TextQuery::run_query(const string &query_word) const {
3     map< string, set<line_no> >::const_iterator loc = word_map.find(query_word);
4     if (loc == word_map.end())
5         return set<line_no>();
6     else
7         return loc->second;
8 }

       4. 實現text_line 函式:

1 string TextQuery::text_line(line_no line) const {
2     if (line < lines_of_text.size()) {
3         return lines_of_text(line);
4     }
5     throw std::out_of_range("line number out of range");
6 }

第三步:編寫主函式( 其實可以和第二步同時進行 提高效率 )

 1 void print_results(const set<TextQuery::line_no> & locs, const string &sought, const TextQuery &file);
 2 ifstream & open_file(ifstream &in, const string &file);
 3 string make_plural(size_t ctr, const string &word, const string &ending);
 4 
 5 int main(int argc, char **argv)
 6 {
 7     ifstream infile;
 8     if (argc < 2 || !open_file(infile, argv[1])) {
 9         cerr << "No input file!" << endl;
10         return EXIT_FAILURE;
11     }
12 
13     TextQuery tq;
14     tq.read_file(infile);
15 
16     while (true) {
17         cout << "inter a word to query:" << endl;
18         string s;
19         cin >> s;
20         if (!cin || s == "q") break;
21         set<TextQuery::line_no> locs = tq.run_query(s);
22         print_results(locs, s, tq);
23     }
24 
25     return 0;
26 }
27 
28 void print_results(const set<TextQuery::line_no> & locs, const string &sought, const TextQuery &file) {
29     // 為了表示下面這個size_type型別,還真煞費苦心。
30     typedef set<TextQuery::line_no> line_nums;
31     line_nums::size_type size = locs.size();
32     cout << endl << sought << " occurs "  << size << " " << make_plural(size, "time", "s") << endl;
33 
34     line_nums::const_iterator it = locs.begin();
35     for (; it != locs.end(); ++it) {
36         cout << "\t(line " << (*it) + 1 << ")" << file.text_line(*it) << endl;
37     }
38 }
39 
40 ifstream & open_file(ifstream &in, const string &file)
41 {
42     in.close();
43     in.clear();
44 
45     in.open(file.c_str());
46 
47     return in;
48 }
49 
50 string make_plural(size_t ctr, const string &word, const string &ending)
51 {
52     return (ctr == 1) ? word : word+ending;
53 
54 }

說明

       第一步,第二步,第三步的程式碼檔案分別為主函式原始碼檔案( .cpp ),類定義標頭檔案( .h )和類實現原始碼檔案( .cpp )。

       在構建這種工程時,標頭檔案和名稱空間的設定是有講究的( 若要生成真正的可執行程式,上面的程式碼還要根據一些原則進行改動 ),本文不進行詳述。

小結

       1. 體驗物件導向”工程“ ( 這一部分目前也有點還沒弄清楚 待深入學習 )

       2. 靈活使用容器

       3. 感受物件導向思想

相關文章