前几天写好了字典,又刚好重温了KMP算法,恰逢遇到朋友吐槽最近被和谐的词越来越多了,于是突发奇想,想要自己实现一下敏感词屏蔽。
基本敏感词的屏蔽说起来很简单,只要把字符串中的敏感词替换成“***”就可以了。对于子串的查找,就KMP算法就可以了。但是敏感词这么多,总不能一个一个地遍历看看里面有没有相应的词吧!
于是我想到了前几天写的字典树。如果把它改造一下,并KMP算法结合,似乎可以节约不少时间。
首先说明一下思路:
对于KMP算法,这里不过多阐述。对于敏感词库,如果把它存进字典树,并在每个节点存上它的next值。在进行匹配的时候,遍历主串,提取出单个字(对于UTF-8编码,可以是任何国家的字),然后去字典树的根结点的unordered_map中进行查找是否存在。如果不存在,则对下一个字进行相同处理;如果存在,则进入该子节点,然后继续查找。字典树结构如下图:
1~6是编号,后面说明一些东西的时候用到。
Root节点里不存任何数据,只是提供一个词典的起始位置。那个表格用的是unordered_map。
对于一个树型结构,如果直接用KMP算法中的next值来确定下一个应该在哪个节点进行查找似乎会有点问题。比如,对于5号节点,next值为1,但是要怎么用这个"1"进入要查找的节点呢?
由于每个节点只需要知道自己如果匹配失败应该跳到哪个节点,我想了以下两种方案:
1、把next改成存着节点的地址,类似线索二叉树,这样可以很方便地进行节点转换。
2、用栈,每次进入子节点,就对原节点的地址进行压栈,next中存的值是要从栈中弹出几个元素。
由于之前的字典树在遍历的时候采用list实现的栈来确定下一个词是哪个,于是我选择用第二种方案。
方案有了,就是如何实现的事了。
我先对字典树的数据结构进行修改:
DictionaryData.h
1 #ifndef __DICTIONARYDATA_H__ 2 #define __DICTIONARYDATA_H__ 3 4 #include <string> 5 #include <unordered_map> 6 #include <memory> 7 8 namespace ccx{ 9 10 using std::string; 11 using std::unordered_map; 12 using std::shared_ptr; 13 14 struct DictElem 15 { 16 string _word; 17 bool _isend;//是否到词尾 18 int _next;//KMP next值 此处有修改,存的是弹栈数量 19 unordered_map<string, shared_ptr<DictElem> > _words; 20 }; 21 22 typedef shared_ptr<DictElem> pDictElem; 23 24 } 25 26 #endif
相应地,字典树的成员函数也要进行修改。
Dictionary.h
1 #ifndef __DICTIONARY_H__ 2 #define __DICTIONARY_H__ 3 4 #include "DictionaryData.h" 5 #include "DictionaryConf.h" 6 7 #include <memory> 8 #include <vector> 9 #include <list> 10 11 namespace ccx{ 12 13 using std::shared_ptr; 14 using std::vector; 15 using std::list; 16 using std::pair; 17 18 class Dictionary 19 { 20 typedef pair<int, int> Loc; 21 typedef unordered_map<string, pDictElem>::iterator WordIt; 22 public: 23 Dictionary(); 24 void push(const string & word);//插入 25 void push(vector<string> & words);//插入 26 bool search(const string & word);//查找 27 bool associate(const string & word, vector<string> & data);//联想 28 string Kmp(const string & word); 29 30 private: 31 bool Kmp(vector<string> & word, vector<Loc> & loc); 32 void getKmpNext(const vector<string> & characters, vector<int> & next); 33 void AddWord(const string & word); 34 void splitWord(const string & word, vector<string> & characters);//把词拆成字 35 int search(vector<string> & data, pDictElem & pcur); 36 pDictElem _dictionary; 37 DictionaryConf _conf; 38 39 //遍历 40 public: 41 string getCurChar(); 42 string getCurWord(); 43 bool isEnd(); 44 void resetIt(); 45 void next(); 46 private: 47 void resetPoint(pDictElem pcur); 48 void next(pDictElem & pcur, list<WordIt> & stackWord, list<pDictElem> & stackDict); 49 void nextWord(pDictElem & pcur, list<WordIt> & stackWord, list<pDictElem> & stackDict); 50 string getCurWord(list<WordIt> & stackWord); 51 52 pDictElem _pcur; 53 WordIt _itcur; 54 55 //用list实现栈,遍历时方便 56 list<WordIt> _stackWord; 57 list<pDictElem> _stackDict; 58 59 //导入导出 60 public: 61 void leading_in(); 62 void leading_out(); 63 }; 64 65 } 66 67 #endif