/// <summary> /// 使用用户词典合并粗分结果,并将结果收集到全词图中,然后返回合并后的结果 /// In place modify /// </summary> /// <param name="vertices"></param> /// <param name="wordNet"></param> /// <returns></returns> protected static List <Vertex> CombineByCustomDict(List <Vertex> vertices, WordNet wordNet) { var list = CombineByCustomDict(vertices); // 合并,产生 最长匹配 int line = 0; // 行号 //! 索引 for (int i = 0; i < list.Count; i++) { var vertex = list[i]; // 当前词条 var parentLen = vertex.realWord.Length; // 当前词条的字符串长度 int currLine = line; // 获取当前行号 if (parentLen >= 3) // 长词条, { Action <int, int, WordAttr> action = (begin, end, value) => { if (end - begin == parentLen) { return; } wordNet.Add(currLine + begin, new Vertex(vertex.realWord.Substring(begin, end), value)); }; CustomDictionary.Parse(vertex.realWord, action); } line += parentLen; } return(list); }
/// <summary> /// 生成一元词网 /// </summary> /// <param name="wordNet">初创词网对象</param> protected void GenerateWordNet(WordNet wordNet) { var chars = wordNet.charArr; // 原始句子的字符数组 var searcher = CoreDictionary._trie.GetSearcher(chars, 0); // 获取核心词典词语搜索器:搜索核心词典中的词条 while (searcher.Next()) { // searcher.begin + 1 -> 由于存在起始辅助节点,所以每个节点的索引向后偏移 1 。 wordNet.Add(searcher.begin + 1, new Vertex(new string(chars, searcher.begin, searcher.length), searcher.value, searcher.index)); } var vertices = wordNet.Vertices; // 上一步中,仅仅是根据核心词典中的词条来划分原始句子,所以还需要对句子中除了核心词汇之外的部分进行分词 // 比如“这是一个操蛋的世界”,假设核心词汇为“一个”、“世界”,那显然剩余部分——“这是”、“操蛋的”——还需要进行分词处理 // 然而,我们这里先使用快速原子分词,后面再进一步使用专用分词器分词 for (int i = 1; i < vertices.Length;) { if (vertices[i].Count == 0) // 如果当前行没有顶点 { int j = i + 1; // 往后遍历,找到下一个存在节点的行 for (; j < vertices.Length - 1; j++) { if (vertices[j].Count != 0) { break; } } wordNet.Add(i, QuickAtomSegment(chars, i - 1, j - 1)); // i-1,j-1,这里因为有起始辅助节点,导致下标 i,j 均向后偏移一位,所以需要减去 1。 i = j; // 更新当前位置到下一个有节点的行号位置 } else { i += vertices[i].Last().realWord.Length; } } }
/// <summary> /// 合并数量词 /// </summary> /// <param name="list"></param> /// <param name="wordNet"></param> /// <param name="config"></param> protected void CombineNumQuant(List <Vertex> list, WordNet wordNet, SegConfig config) { if (list.Count < 4) { return; // 除去首尾辅助节点2个,还需要至少2个有效节点才能合并 } var sb = new StringBuilder(); int line = 1; // 初始化为1,跳过起始辅助节点 for (int i = 0; i < list.Count; i++) { var pre = list[i]; if (pre.HasNature(Nature.m)) // 第一个数词出现 { sb.Append(pre.realWord); Vertex cur = null; i++; while (i < list.Count) // 遍历数词之后的词 { cur = list[i]; if (cur.HasNature(Nature.m)) // 连续数词 { sb.Append(cur.realWord); list[i] = null; RemoveFromWordNet(cur, wordNet, line, sb.Length); i++; } else { break; } } if (cur != null) // 合并连续的数词后,遇到的第一个非数词 { if (cur.HasNature(Nature.q) || cur.HasNature(Nature.qv) || cur.HasNature(Nature.qt)) // cur 是量词 { if (config.indexMode) // 如果开启索引(最小)分词模式,则先将连续数词作为一个节点添加进词网 { wordNet.Add(line, new Vertex(sb.ToString(), new WordAttr(Nature.m))); } sb.Append(cur.realWord); // 合数量词 list[i] = null; RemoveFromWordNet(cur, wordNet, line, sb.Length); } else // 遇到第一个非数词,也非量词 { line += cur.realWord.Length; // 更新行号,表示跳过这个(非数非量)词 } } if (sb.Length != pre.realWord.Length) // 长度不等,意味着合并到连续数词或者数量词 { foreach (var vertex in wordNet.GetRow(line + pre.realWord.Length)) // 遍历第一个数词之后的行号上的所有节点列表,将这些节点的前驱节点置为空 { vertex.from = null; } pre.realWord = sb.ToString(); // 首个数词修改为连续数词 或者 数量词 pre.word = TAG_NUMBER; // 实际上,本项目将数量词等同数词处理了 pre.attr = new WordAttr(Nature.mq); pre.wordId = CoreDictionary.M_WORD_ID; sb.Clear(); } } sb.Clear(); line += pre.realWord.Length; // 更新行号,跳过合并后的数量词 } }