首页 > C++ 容器的综合应用的一个简单实例——文本查询程序

C++ 容器的综合应用的一个简单实例——文本查询程序

[0. 需求]

最近在粗略学习《C++ Primer 4th》的容器内容,关联容器的章节末尾有个很不错的实例。

通过实现一个简单的文本查询程序,希望能够对C++的容器学习有更深的理解。

由于是浅略探讨研究,高手可无视,各位读者发现有什么不妥的地方,请指教。

程序将读取用户指定的任意文本文件,然后允许用户从该文件中查找单词。

查询的结果是该单词出现的次数,并列出每次出现所在的行。

如果某单词在同一行中多次出现,程序将只显示该行一次。

行号按升序显示,即第 1行应该在第 2 行之前输出,依此类推。

本人用到的文本文件“ inputfile.txt ”,内容如下(显示的行号并非文件原内容):

1 Our program will read a file specified by the user and then allow the user to 
2 search the file for words that might occur in it. The result of a query will be 
3 the number of times the word occurs and a list of lines on which 
4 it appears. If a word occurs more than once on the same line, 
5 our program should be smart enough to display that line only once. 
6 Lines should be displayed in ascending orderthat is, 
7 line 7 should be displayed before line 9, and so on.

[1. 程序的设计]

设计程序的一个良好习惯是首先将程序所涉及的操作列出来。

这样有助于建立需要的数据结构和实现这些行为。

本程序的需求如下:

它必须允许用户指明要处理的文件名字。

程序将存储该文件的内容,以便输出每个单词所在的原始行。

它必须将每一行分解为各个单词,并记录每个单词所在的所有行。

在输出行号时,应保证以升序输出,并且不重复。

对特定单词的查询将返回出现该单词的所有行的行号。

输出某单词所在的行文本时,程序必须能根据给定的行号从输入文件中获取相应的行。

1.1 数据结构

我们将用一个简单的类 TextQuery ,再配合几种容器的使用,实现这个程序的要求。

使用一个 vector 类型的对象存储整个输入文件的副本。

输入文件的每一行是该 vector 对象的一个元素。

因而,在希望输出某一行时,只需以行号为下标获取该行所在的元素即可。

将每个单词所在的行号存储在一个 set 容器对象中。

使用 set 就可确保每行只有一个条目,而且行号将自动按升序排列。

使用一个 map 容器将每个单词与一个 set 容器对象关联起来,该 set 容器对象记录此单词所在的行号。

综上所述,我们定义的 TextQuery 类将有两个数据成员:

1. 储存输入文件的 vector 对象;

2. map 容器对象(其关联每个输入的单词和记录该单词所在行号的 set 容器对象)。

1.2 操作

对于类还要求有良好的接口。

查询函数需返回存储一组行号的 set 对象。这个返回类型应该如何设计呢?

事实上,查询的过程相当简单:使用下标访问 map 对象获取关联的 set 对象即可。



唯一的问题是如何返回所找到的 set 对象。安全的设计方案是返回该 set 对象的副本。

但如此一来,就意味着要复制 set 中的每个元素。

如果处理的是一个相当庞大的文件,则复制 set 对象的代价会非常昂贵。

其他可行的方法包括:

返回一个 pair 对象,存储一对指向 set 中元素的迭代器;或者返回 set 对象的 const 引用。



为简单起见,我们在这里采用返回副本的方法,

但注意:如果在实际应用中复制代价太大,需要新考虑其实现方法。

类的接口需提供下列三个 public 函数:

1. read_file 成员函数,其形参为一个 ifstream& 类型对象。

  该函数每次从文件中读入一行,并将它保存在 vector 容器中。

  输入完毕后,read_file 将创建关联每个单词及其所在行号的 map 容器。

2. run_query 成员函数,其形参为一个 string 类型对象,返回一个 set 对象,

  该 set 对象包含出现该 string 对象的所有行的行号。

3. text_line 成员函数,其形参为一个行号,返回输入文本中该行号对应的文本行。

  无论 run_query 还是 text_line 都不会修改调用此函数的对象,

  因此,可将这两个操作定义为 const 成员函数。

  为实现 read_file 功能,还需定义两个 private 函数来读取输入文本和创建 map 容器:

4. store_file 函数读入文件,并将文件内容存储在 vector 容器对象中。

5. build_map 函数将每一行分解为各个单词,创建 map 容器对象,同时记录每个单词出现的行号。

[2. TextQuery 类]

定义 TextQuery 类的头文件 “ TextQuery.h ” 内容如下:

#ifndef __TEXTQUERY_H__
#define __TEXTQUERY_H__#include 
#include 
#include 
#include 
#include 
#include <set>
#include 
#include <string>typedef std::vectorstring>::size_type line_no;class TextQuery {public:// interface:void read_file(std::ifstream &is) {store_file(is);build_map();}std::set run_query(const std::string &) const;std::string text_line(line_no) const;private:// utility functions used by read_filevoid store_file(std::ifstream&);void build_map(); // associate each word with a set of line numbers// remember the whole input filestd::vectorstring> lines_of_text;// map word to set of the lines on which it occursstd::map< std::string, std::set > word_map;
};#endif

注意:这个类的定义中,在引用标准库内容时都必须完整地使用 std:: 限定符。

read_file 函数在类的内部定义。该函数首先调用 store_file 读取并保存输入文件,

然后调用 build_map 创建关联单词与行号的 map 容器。

[3. TextQuery 类的使用]

下面的主程序 main 使用 TextQuery 对象实现简单的用户查询会话。

这段程序的主要工作是实现与用户的互动:

提示输入下一个要查询的单词,然后调用 print_results 函数输出结果。

3.1 引子

程序首先检查 argv[1] 是否合法,然后调用 open_file 函数打开以 main 函数实参形式给出的文件。

检查流以判断输入文件是否正确。

如果不正确,就给出适当的提示信息结束程序的运行,返回 EXIT_FAILURE 说明发生了错误。

一旦文件成功打开,建立支持查询的 map 容器就相当简单。

open_file 函数的实现如下:

// opens in binding it to the given file
ifstream& open_file(ifstream &in, const string &file)
{in.close();     // close in case it was already openin.clear();     // clear any existing errors// if the open fails, the stream will be in an invalid statein.open(file.c_str()); // open the file we were givenreturn in; // condition state is good if open succeeded
}

3.2 实现查询

为了使用户在每次会话时都能查询多个单词,我们将提示语句也置于 while 循环中:

#include "TextQuery.h"define    EXIT_FAILURE    -1using namespace std;int main(int argc, char *argv[])
{// open the file from which user will quer words 
    ifstream infile;if(argc < 2 || !open_file(infile, argv[1])){cerr << "No input file!" << endl;return EXIT_FAILURE;}TextQuery tq;tq.read_file(infile);// prompt for a word to file and print resultwhile(true){cout << "Enter word to look for(or Q/q to Quit): 
" ;string str;cin >> str;// stop if hit eof on input or a 'Q' is entered if(!cin || str == "Q" || str == "q")break;// get tje set of line numbers on which this word appearsset locs = tq.run_query(str);// print count an all occurrences, if any
        print_results(locs, str, tq);}return 0;
}

while 循环条件为布尔字面值 true,这就意味着循环条件总是成立。

在检查 cin 和读入 s 值后,由紧跟的 break 语句跳出循环。

具体说来,当 cin 遇到错误或文件结束,或者用户输入 q 时,循环结束。

每次要查找一个单词时,访问 tq 获取记录该单词出现的行号的 set 对象。

将 set 对象、要查找的单词和 TextQuery 对象作为参数传递给 print_results 函数,该函数输出查询结果。

3.3 输出结果

输出时,首先给出查询到的匹配个数,即 set 对象的大小。

然后调用一个 make_plural 函数,根据 size 是否为 1 输出“time”或“times”。

// return plural version of word if ctr isn't 1
string make_plural(size_t ctr, const string &word,const string &ending)
{return (ctr == 1) ? word : word + ending;
}
void print_results(const set& locs, const string& sought, const TextQuery& file)
{// if the word was found, then print count and all occurrencestypedef set line_nums;line_nums::size_type size = locs.size();cout << "
" << sought << " occurs " << size << " "<< make_plural(size, "time", "s") << endl;// print each line in which the word appearedline_nums::const_iterator it = locs.begin();for(; it != locs.end(); ++it){// don't confound user with text lines starting at 0cout << "	(line" << (*it) +1 << ")" << file.text_line(*it) << endl;}
}

 

以上几个函数定义和实现都放在 main.cpp 文件之中。

注意:为了与 C++ 的容器和数组下标编号匹配,在储存文本时,我们以行号 0 存储第一行。

但考虑到很多用户会默认第一行的行号为 1,所以输出行号时,

相应地所存储的行号上加 1 使之转换为更通用的形式。

[4. 成员方法的实现]

TextQuery 类的成员方法实现 “ TextQuery.cpp” 文件内容如下:

#include "TextQuery.h"
#include 
#include using namespace std;set TextQuery::run_query(const string &query_word) const
{// must use find and not subscript he map directly// to avoid adding words to word_mapmap< string, set >::const_iteratorloc = word_map.find(query_word);if(loc == word_map.end()){// not found, return empty setreturn set(); }else{// fectch and return set of line numbers for this wordreturn loc->second;    }
}string TextQuery::text_line(line_no line) const
{if(line < lines_of_text.size())return lines_of_text[line];throw std::out_of_range("line number out of range");
}// utility functions used by read_file
void TextQuery::store_file(ifstream &is)
{string textline;while (getline(is, textline))lines_of_text.push_back(textline);
}// associate each word with a set of line numbers
void TextQuery::build_map()
{// process each line from the input vectorfor (line_no line_num = 0; line_num != lines_of_text.size(); ++line_num) {// we'll use line to read the text a word at a time
        istringstream line(lines_of_text[line_num]);string word;while (line >> word){// add this line number to the set;// subscript will add word to the map if it's not already there
            word_map[word].insert(line_num);}} 
}

[5. 编译运行]

之前一直是用 CFree5.0 做的《C++ Primer 4th》的练习,

但是由于没找到怎么手动添加参数运行程序,

>_<|||...有知道怎么搞的,还望不吝赐教。

没办法最后选了在 cygwin 下编译,命令如下:

编译成功后,文件如下:

运行,命令如下:

结果显示如下:

根据运行结果观察,应该是实现了预期的程序功能。O(∩_∩)O 哈哈~

转载于:https://www.cnblogs.com/yshl-dragon/p/3152803.html

更多相关:

  • 为了不想让竞争对手看到我们使用的服务器的技术细节,我们可以将线上运行的ATS修改为我们自己设置的名字以及相应的版本号。在线修改如下选项 traffic_line -s proxy.config.http.response_server_str -v pcs/1.0.1 traffic_line -s proxy.config.htt...

  • 转载https://blog.csdn.net/qq_35883464/article/details/83151464 实现员工信息表文件存储格式如下:id,name,age,phone,job1,Alex,22,13651054608,IT2,Egon,23,13304320533,Tearcher3,nezha,25,1333...

  • oracle 10g: PL/SQL User's Guide and Reference ---> 10 Handling PL/SQL Errors ---> Summary of Predefined PL/SQL Exceptions 系统预定义异常(有名字的错误代码):TOO_MANY_ROWS : SELECT INTO...

  • 1. set的初始化 set可以这样初始化 set iset{ 1,2,3 }; set iset2 = { 1,2,3 }; 在初始化set的时候,若出现重复的元素 set iset3{ 1,2,3,3,3 }; set iset4 = { 1,2,3,3,3 };...

  •     Set 对象存储的值总是唯一的 Set 对象方法 方法描述add添加某个值,返回Set对象本身。clear删除所有的键/值对,没有返回值。delete删除某个键,返回true。如果删除失败,返回false。forEach对每个元素执行指定操作。has返回一个布尔值,表示某个键是否在当前 Set 对象之中。 Set 对象...

  • 我现在的vimrc配置文件 runtime! debian.vim "设置编码 set encoding=utf-8 set fencs=utf-8,ucs-bom,shift-jis,gb18030,gbk,gb2312,cp936 set fileencodings=utf-8,ucs-bom,chinese"语言设置...

  • ACCEPT acm作业 http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=204 因为老师是在集合那里要我们做这道题。所以我很是天真的就以为要用集合做,结果发现网上都是用数组简单明了地实现了,显得我的代码,特么地超级恶心!!!!!!! 在这里存档一下,别人就不要看了...

  • USE [svnhost]GO/****** 对象:  StoredProcedure [dbo].[up_Page2005]    脚本日期: 05/21/2008 11:27:05 ******/SET ANSI_NULLS ONGOSET QUOTED_IDENTIFIER ONGOCREATE proc [dbo].[up_P...

  • 点云PCL免费知识星球,点云论文速读。文章:DSP-SLAM: Object Oriented SLAM with Deep Shape Priors作者:Jingwen Wang Martin Runz Lourdes Agapito编译:点云PCL代码:https://github.com/JingwenWang95/DSP-S...

  • RAM缓存 新RAM缓存算法(CLFUS) 新的RAM缓存使用的创意来自许多缓存替换策略和算法,包括LRU,LFU,CLOCK,GDFS及2Q,它被命名为时钟周期内最小频繁使用大小算法CLFUS(Clocked Least Frequently Used by Size)。它避开了任何专利算法,具有如下特性: 均衡最近性(Rec...

  • MP4 |视频:AVC,1280×720 30 fps |音频:AAC,48 KHz,2 Ch |时长:2h 12m 语言:英语+中英文字幕(根据原英文字幕机译更准确)|大小解压后:560M C4D是一个有抱负的运动图形艺术家和设计师的重要工具。借助C4D,您可以使用3D对象、动态效果和动画来增强运动图形、模型和可视化效果。本课...

  • 文章目录先说问题:再说解决尝试1:尝试2(该尝试建议先在自己环境搭配对应业务测试通过后再现场尝试): 感谢 学无止境996同学的陪伴和vigourtyy美丽女友的支持,直到这个解决问题的深夜 先说问题: ceph 12.2.1生产环境:3副本 tier + 3副本data 机房在拥有业务的情况下重启集群交换机,产生如下场景...

  • 这周主要学习了java中的类和对象的知识点,发现和C++中的类和对象极为相似,对于类和对象的概念理解起来也简单。同时在自学的过程中也把类的知识重新复习巩固了一下(如类的三大特征:继承,封装和多态,构造,成员对象的访问权限,构造,无参有参函数的调用等),同时也了解到一些新的概念,比如类对象创建和引用占据堆内存和栈内存,输出对象时默认调...