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 行。

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

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

 

ExpandedBlockStart.gif 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) 

转载于:https://www.cnblogs.com/heqile/archive/2011/04/23/2025421.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值