C++实践笔记(二)----实现一个简单的文本查询程序

学到关联容器,10.6,容器的综合应用:文本查询程序,决定不看课本实现它.

题目如下:

------------------------------------------------------------------------------

我们将实现一个简单的文本查询程序来结束本章。
我们的程序将读取用户指定的任意文本文件,然后允许用户从该文件中查找
单词。查询的结果是该单词出现的次数,并列出每次出现所在的行。如果某单词
在同一行中多次出现,程序将只显示该行一次。行号按升序显示,即第 7 行应
该在第 9 行之前输出,依此类推。
例如,以本章的内容作为文件输入,然后查找单词“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.
后面省略了大约 120 行。

------------------------------------------------------------------------------

看完题目,写了一个程序如下:

 

View Code
ifstream &open_file(ifstream &in,const string &file)
{
    
in.close();
    
in.clear();
    
in.open(file.c_str());
    
return in;
}

int main(int argc, char* argv[])
{
    ifstream ifst;
    
if(!open_file(ifst,argv[1]))//文件名通过命令行指定
        throw runtime_error("没有找到指定的文本文件!");
    cout
<<"请输入要查找的单词:"<<flush;
    
string target;
    cin
>>target;

    typedef multimap
<int,string> Direct;
    Direct result;
    
int line_no=1;
    
string line;
    
while(getline(ifst,line))//读文件的每一行
    {
        istringstream istring(line);
        
string word;
        
while(istring>>word)//读这一行的每个单词
        {
            
if(word==target)
                result.insert(make_pair(line_no,line));
//如果出现了要找的单词,把这一行放进multimap
        }
        line_no
++;//下一行
    }

    cout
<<target<<" occurs "<<result.size()<<" times"<<endl;//共出现的次数
    Direct::iterator iter=result.begin();
    
while(iter!=result.end())
    {
        cout
<<"(line "<<iter->first<<""<<iter->second<<".";//输出行
        int wcount=result.count(iter->first);
        
if(wcount>1)
            cout
<<"("<<wcount<<" times)";//如果一行出现了大于1次,在后面注上这一行出现在这个单词的次数
        cout<<endl;
        iter
=result.upper_bound(iter->first);//下一行
    }

    system(
"PAUSE");
    
return 0;
}

写完了感觉不对啊,好像课本上这一节挺长的啊,就这么点?然后看了看10.6.1小节设计的部分.顿时觉得我那刚才写的能叫程序吗…首先,只能满足查询一次,查完了就退出了;其次,每次执行这些代码都要开辟大量的存储空间用来存储行的内容,如果文本相当大那么代价太高;再次,如果把这项功能想成一个模块,是写给另一个程序员使用的,或许更贴近实际;最后,bug,贴着单词的标点会被当成是这个单词的一部分.好,重写.

首先,定义一个类,类名就跟课本一致,TextQuery,至于成员我定义了三个公共的成员函数和两个私有成员:

class TextQuery
{
public:
    
void input_file(ifstream &);//从文件读入文本,建立起result和text.
    set<int> get_list(const string);
            
//查询单词在哪些行出现了,行号在返回的set容器中.
    string &get_line(int);//得到行的内容,返回引用,代价较小.
private:
    vector
<string> text;//储存了文本文件的每一行.
    map<string,set<int> > result;
            
//result将每个单词与记录它出现的行数的set容器关联起来.
};

 下面是主程序,这里将所有的单词全部转换为小写,也就是说查找的时候不进行大小写匹配:

int _tmain(int argc, _TCHAR* argv[])
{
    
string file_name;
    cout
<<"请输入文件名:"<<flush;
    cin
>>file_name;
    ifstream ifst(file_name.c_str());
    
if(!ifst)
    {
        cout
<<"文件不存在!"<<endl;
        system(
"PAUSE");
        
return -1;
    }
    TextQuery artical;
    artical.input_file(ifst);
    
string target;
    cout
<<"请输入要查找的单词(以Ctrl+Z结束):"<<flush;
    
while(cin>>target)
    {
        target
=to_lower(target);//不匹配大小写
        set<int> line_list=artical.get_list(target);//获得行号
        cout<<target<<" occurs "<<line_list.size()<<" times"<<endl;//一行出现了多次也只算一次
        for(set<int>::iterator iter=line_list.begin();iter!=line_list.end();iter++)
            cout
<<"(line "<<*iter<<""
            
<<artical.get_line(*iter)<<endl;//输出每一行
        cout<<"请输入要查找的单词(以Ctrl+Z结束):"<<flush;
    }
    system(
"PAUSE");
    
return 0;
}

 其中的to_lower()函数定义如下(使用了cctype头文件):

string to_lower(string word)
{
    
for(string::iterator iter=word.begin();iter!=word.end();iter++)
        
if(isupper(*iter))*iter=tolower(*iter);
    
return word;
}

下面实现类里面的三个公共成员函数:

1.最重要的就是input_file()函数,它读取文件中的文本,建立起map容器和vector容器供以后查询使用,注意在建立map容器的时候要准确找到每个单词,不能将标点符号也算做单词的一部分:

void TextQuery::input_file(ifstream &file)
{
    
string line;//行文本
    string::iterator char_iter;//指向字符元素的迭代器
    int flag;//标志,0表示没有遇到字母,1表示在收集连续的字母组成单词的过程中.
    string word;//单词
    text.push_back("");//从text[1]开始储存文本,跟行号对应
    int line_no=1;//
    while(getline(file,line))
    {
        text.push_back(line);
//建立起vector型容器text
        char_iter=line.begin();
        flag
=0;
        
while(char_iter!=line.end())
        {
            
char c=*char_iter;
            
if(isletter(c))//收集连续的字母组成单词
            {
                
if(isupper(c))c=tolower(c);//不匹配大小写
                if(flag==0)flag=1;
                word.push_back(c);
            }
            
if(flag==1&&((char_iter==line.end()-1)||!isletter(c)))//不考虑一个单词被断开在两行出现
            {
                result[word].insert(line_no);
//如果没有键为word的元素则会自动建立一个再执行insert().
                word.clear();//清空word
                flag=0;//重置标志位
            }
            char_iter
++;//下一个字符
        }
        line_no
++;//下一行
    }
}

其中判断是否是字母的函数isletter()如下:

inline bool isletter(char c)
{
    
return ((c>=97&&c<=122)||(c>=65&&c<=90)||(c>=48&&c<=57));//可以识别1st这样的单词,但it's要算两个单词.
}

2.查询函数get_list(),返回一个set容器,里面有出现了所查单词的行的行号:

set<int> TextQuery::get_list(const string word)
{
    
set<int> se;
    map
<string,set<int> >::iterator word_iter=result.find(word);
    
if(word_iter!=result.end())
        se
=word_iter->second;
    
return se;
}

3.打印行文本的函数get_line():

string &TextQuery::get_line(int i)
{
    
return text[i];
}

 写完之后自我感觉良好.然后看课本,感觉有些地方课本上的写法确实有她的好处,比如储存行号的set容器,我的定义是set<int>,而课本上是:

typedef std::vector<std::string>::size_type line_no;然后set<line_no>,这样就能保证line_no的范围合适,并且程序更具逻辑性,更易读.

再比如课本上将我写的input_file()函数分成了两个函数store_file()和build_map();这样也使得程序更易读.但我觉得这样做的代价更大,因为分成两个函数与一个函数比起来多了一次对vector容器的遍历.

不过,我也有比课本写得更完善一点的地方, 就是对于单词的判断上,课本的程序只通过空格来判断,误差比较大.

附:代码文件(环境是VC++ 2008) 

posted on 2011-04-23 05:01  Barryhe  阅读(2589)  评论(0编辑  收藏  举报

导航