最短摘要生成

题目:抽象点说,就是在一个字符串中,找一些目标字符串,找到包含所有目标字符串的最小字符串。题目虽然叫做最短摘要生成,但和实际的搜索snippet的计算还是有比较大的差距的。

解法:编程之美3.5给出了一种比较好的解法,策略还是使用双指针,双指针对于很多算法设计很有价值,算法的思想是采用两个指针,开始两个指针都指向缓冲区的头部,尾指针向后扫描,直到头指针和尾指针中间包含了全部的关键字,那么头指针向后移动,直到包含全部关键字这个条件失败,这时截取字串并和已取得的最小字串比较,如果小则替换。头指针、尾指针都向后一个位置(这点很重要,开始就忘记了移动头指针,导致程序出错),继续扫描。另外,由于一个关键字可能重复多次,因此在判断是否包含全部关键字时要采用计数机制,才能保证查看的准确。这样只要头指针和尾指针扫描两次字符串就可以完成生成算法。

<p>#include <stdio.h>
#include <string>
#include <map>
class KeyWordChecker {
 public:
  KeyWordChecker() : current_state_(false) {}
  // 添加KeyWord
  void AddKeyWord(const std::string& word) {
    if (keywords_.find(word) == keywords_.end()) {      
      keywords_[word] = 0;
    }
  }
  // 查看当前(begin,end)字符串是否包含所有KeyWord
  bool IsContainAllKeyWord(const std::string& word, bool add) {
    if (keywords_.find(word) == keywords_.end()) {
      return current_state_; // 字符串word不是KeyWord
    }
    if (add) {
      keywords_[word]++; //相应KeyWord次数增加
    } else {
      keywords_[word]--; //相应KeyWord次数减少
    }
 // 获得KeyWord迭代器,遍历查看当前(begin,end)字符串中包含的KeyWord个数
    std::map<std::string, int>::iterator it = keywords_.begin();
    int counter = 0;
    while (it != keywords_.end()) {
      if (it->second > 0) {
        counter++;
      } else {
        break;
      }
      it++;
    }
    if (counter == keywords_.size()) { //包含所有KeyWord
      current_state_ = true;
      return true;
    } else {
      current_state_ = false;
      return false;
    }
  }
 private:
  std::map<std::string, int> keywords_;
  bool current_state_;
};</p><p>std::string GenerateSnippet(const std::string& content, KeyWordChecker* keyword_checker) {
  int begin = 0;
  int end = 0;
  std::string snippet;
  int min_length = content.size();
  // string s(num,c) //生成一个字符串,包含num个c字符
  while (end < content.size()) {
 // 当前(begin,end)字符串不包含所有KeyWord
    if (!keyword_checker->IsContainAllKeyWord(std::string(1, content[end]), true)) {
       end++; // 移动尾指针
       continue; // 直到(begin,end)字符串中包含了全部的关键字
    }
 // 依次向后移动头指针,直到包含全部关键字这个条件失败
    while (begin <= end && keyword_checker->IsContainAllKeyWord(std::string(1, content[begin]), false)) {
      begin++; // 移动头指针
    }
 // 截取字串和已取得的最小字串比较,如果小则替换。头指针、尾指针都向后一个位置,继续扫描
    if (end - begin + 1 < min_length) {
      snippet = content.substr(begin, end - begin + 1);
      min_length = end - begin + 1;
    }
    end++;
    begin++;
  }  
  return snippet;
}
int main(int argc, char** argv) {
  std::string content = "abbbbbcaaadebmmmmdcfg";
  KeyWordChecker keyword_checker;
  keyword_checker.AddKeyWord("b");
  keyword_checker.AddKeyWord("d");
  std::string snippet = GenerateSnippet(content, &keyword_checker);
  printf("snippet:%s\n", snippet.c_str());
}
</p>

 

1. 题干大意如下:输入两个字符串,一个表示用户输入的查询,另一个表示一篇文档的内容。对于查询和文档分别进行自动分词后,用户查询和文档内容的两个字符串变为两个词语序列。比如,下面的keyword表示分词后的用户查询,str表示分词后的文档内容。

string keyword[] = { "微软", "计算机", "亚洲", "中国"};
  string str[] = { 
    "微软","亚洲","研究院","成立","于","1998","年",",","我们","的","使命",
    "是","使","未来","的","计算机","能够","看","、","听","、","学",",",
    "能","用","自然语言","与","人类","进行","交流","。","在","此","基础","上",
    ",","微软","亚洲","研究院","还","将","促进","计算机","在","亚太","地区",
    "的","普及",",","改善","亚太","用户","的","计算","体验","。","”"
  };


所要求得是str1中的若干个连续的字符串,假设为str[i]-str[j],其中包含keyword中的所有词语,且str[i]-str[j]是满足这个条件的最短的一个。

2. 思路

    这个主要注意几点:首先,str[i]-str[j]包含keyword里面的所有词语,但是不要求顺序相同,然后,str[i]-str[j]是所有满足这样要求的最短子串。
    主要方法是:deque<index> store: 记录当前摘要的所有单词在str中的下标,map<string, int> record记录所有当前摘要中出现的次数。min_len:当前最短摘要的长度。min_index_first:当前最短摘要的第一个词语在str中的下标,min_index_last:当前最短摘要的最后一个词语在str中的下标。    
    第一步,寻找第一个完整摘要,更新store和record。如果没找到,程序结束。如果找到了计算min_len,min_index_first,min_index_last;,然后进行第二步。
    第二步,队列中去掉一个第一个关键词,同时更新record,如果record中该关键词计数还大于0,这说明虽然当前摘要短了,但是还是完整的,因此这必然是一个更短的摘要,min_len--;min_index_first++; 重复第二步。 如果record中关键词的计数为0了,这说明当前摘要不完整了,需要向后面找缺少的关键词,进入第三步。
   第三步,在当前摘要后面寻找缺少的关键词。如果下标越界了,说明不能再找到完整的摘要了,停止工作即可。如果找到的不是关键词,index++,即向后移动继续找。如果找到的是关键词更新store和record,此时如果找到的关键词刚好还是缺少的关键词,那么转向第二步,无论与否,记得index++先。

3. 代码实现

   大体思路不难,但是把当前摘要的关键词下标序列,当前摘要的关键词计数,最短摘要位置,缺少的关键词,这些数据放在一起,还要更新,逻辑就有点复杂了。写了好一会才写出来,不好说对与不对,毕竟没有完整的测试。 

#include<iostream>
#include<string>
#include<map>
#include<deque>
using namespace std;

void find_min_len_abstract(string str[], string keyword[], int len_str, int len_keyword) {
// 初始化map 
  map<string,int> record;
  for(int i=0; i<len_keyword; i++) {
    record[keyword[i]] = 0;
  }
  // 匹配过程
  deque<int> store; // 存储的是str中关键词的下标 
  int min_len = 0;
  int min_index_first = -1;
  int min_index_last = -1;
  int find_key_num = 0;
  int index = 0;
  while(find_key_num < len_keyword && index < len_str) {
    if(record.find(str[index]) == record.end()) { // str[index]不是关键字 
       index++;
    }
    else { // str[index]是关键字 
      if(record[str[index]] == 0) // 第一次找到这个关键字
        find_key_num ++;
      record[str[index]] = record[str[index]] + 1; // 计数加1
      store.push_back(index); 
      index++;
    }
  }
  if(find_key_num < len_keyword) { // 一个满足的摘要都没找到 
    cout << "not abstract found " << endl;
  }
  else { // 找到一个了,试着找找更好的 
    min_len = store.back()- store.front() + 1;   
    min_index_first = store.front();
    min_index_last = store.back();
    // 第一个摘要 
    cout << "第一个摘要" << endl; 
    cout << "min len: " << min_len << endl;
    for(int i=min_index_first; i<=min_index_last; i++)
      cout << str[i] << " ";
    cout << endl; 
    cout << "---------------------------------------------" << endl;
    string need_key;
    bool already_found = true;
    while(true) {   
      if(already_found == true) { // 刚好找到一个新摘要 
        string first_key = str[store.front()];
        record[first_key]--; // 减少当前最前面的关键词 
        store.pop_front(); // 关键词出队 
        if(record[first_key] == 0) { // 如果该关键词没了 
          already_found = false;
          need_key = first_key; // 记录需要寻找的关键词 
        }               
        else { // 少了词语,但是还包含所有关键词,说明这是一个更短的摘要 
          min_len--;
          min_index_first ++;
          cout << "更短的摘要" << endl;
          cout << "min len: " << min_len << endl;
          for(int i=min_index_first; i<=min_index_last; i++)
            cout << str[i] << " ";
          cout << endl; 
          cout << "---------------------------------------------" << endl;
        }
      }
      else { // 需要向后面找满足条件的关键词 
        if(index >= len_str) { // 不可能找到需要的关键词了 
          break;
        }
        else if(record.find(str[index]) == record.end()) { // 不是关键词 
          index++;
        }
        else { // 是关键词 
          record[str[index]] = record[str[index]]+1;
          store.push_back(index); 
          if(str[index] == need_key) { // 正好还是需要找到的关键词
             already_found = true;
             if((store.back()-store.front() + 1) < min_len) { // 新的摘要更短 
               min_len = store.back() - store.front() + 1;
               min_index_first = store.front();
               min_index_last = store.back();
               // 更短的摘要
               cout << "更短的摘要" << endl;
               cout << "min len: " << min_len << endl;
               for(int i=min_index_first; i<=min_index_last; i++)
                 cout << str[i] << " ";
               cout << endl; 
               cout << "---------------------------------------------" << endl;
             }
             else {
               cout << "并非更短的摘要" << endl;
               cout << "min len: " << store.back() - store.front() + 1 << endl;
               for(int i=store.front(); i<=store.back(); i++)
                 cout << str[i] << " ";
               cout << endl; 
               cout << "---------------------------------------------" << endl;
             }
          }
          index++; 
        } // else
      } // else
    } // while
  } // else
  // 输出结果
}

int main() {  
  // string keyword[] = { "微软", "计算机", "亚洲", "中国"};
  string keyword[] = { "微软", "计算机", "亚洲"};
  string str[] = { 
    "微软","亚洲","研究院","成立","于","1998","年",",","我们","的","使命",
    "是","使","未来","的","计算机","能够","看","、","听","、","学",",",
    "能","用","自然语言","与","人类","进行","交流","。","在","此","基础","上",
    ",","微软","亚洲","研究院","还","将","促进","计算机","在","亚太","地区",
    "的","普及",",","改善","亚太","用户","的","计算","体验","。","”"
  };
  int len_keyword = sizeof(keyword)/sizeof(string);
  int len_str = sizeof(str)/sizeof(string);
  find_min_len_abstract(str, keyword, len_str, len_keyword);
  
  system("PAUSE");
  return 0;
}


输出结果如下:

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值