参考:https://github.com/ustcdane/annotated_jieba
const Jieba = require('ooooevan-jieba')
const jieba = new Jieba()
const result = jieba.cut("今天天气很好,🙋 我们去郊游。",{
cutAll: false,
dag: false,
hmm: true,
cutForSearch: false
})
// ['今天天气', '很', '好', ',', '🙋', ' ', '我们', '去', '郊游', '。']
实现了cut、cutHMM、cutAll、cutForSearch、getDag、tag、load方法
如:
str = '到MI京研大厦'
// nodejieba的结果
nodejieba.cut(str); // ['到', 'M', 'I', '京', '研', '大厦']
nodejieba.cut(str, true); // ['到', 'MI', '京研', '大厦']
nodejieba.cutHMM(str); // [ '到', 'MI', '京', '研大厦' ]
// 自己的结果
cut(str, true); // [ '到M', 'I京', '研', '大厦' ]
hmm(str); // ['到M', 'I京', '研大', '厦']
通过调试看源码,发现了hmm用到了很多的正则匹配。
const re_userdict = /^(.+?)( [0-9]+)?( [a-z]+)?$/
const re_eng = /[a-zA-Z0-9]/
const re_han_default = /([\u4E00-\u9FD5a-zA-Z0-9+#&\._%]+)/
const re_skip_default = /(\r\n|\s)/
const re_han_cut_all = /([\u4E00-\u9FD5]+)/
const re_skip_cut_all = /[^a-zA-Z0-9+#\n]/
const han_reg = /([\u4E00-\u9FD5]+)/;
const seg_reg = /([a-zA-Z0-9]+(?:\.\d)?)|([0-9]+(?:\.\d)?%?)/;
大致策略是先将中文和非中文分开,对于非中文,再区分普通的英文数字和其他特殊字符
使用了一些正则,就能得到一致的结果
在之前,我将用户输入构建构建成树时非常繁琐复杂,浪费很多内存,容易造成内存不足
// 之前的方法
var str = "我是拖拉机学院手扶拖拉机专业的。不用多久,我就会升职加薪,当上CEO,走上人生巅峰。";
const treeObj = getWordTree(str); //将字符串转为一个大对象,里面包含需要的字符节点相关数据
var treeArr = recursion(treeObj); //将大对象转为同等的二维数组,里面有2**n个数组,也就是2**n个不同路径
var conarr = getAppropriate(treeArr); //从2**n个数组中选出最优的一个数组
这种方法很直白,是我根据hmm算法描述写的,有太多不需要的数据,导致内存可能爆掉。是2**n
个而不是4**n
,因为每个字符在所在位置都只有两种类型而不是四种。如开头字符只可能是B或S,不能是E或M。
str = '程序员'
treeObj = {
b:{
next:{
e:{
next:{...}
},
m:{
next:{...}
}
},
value: -7.5608
},
s:{
next:{
s:{
next:{...}
},
b:{
next:{...}
}
},
value: -11.0326
}
}
treeArr = [ //长度是8
[{type:B,value:-7.5608},{type:E,value:-8.363},{type:B,value:-9.5638}],
[]
...
]
conarr = [ //最优路径
{type: "B", value: -7.5608126080925, string: "程"},
{type: "M", value: -9.818418731874154, string: "序"},
{type: "E", value: -6.2620785681194855, string: "员"}
]
导致内存不足后,修改为官方的方式,思路如下小
str = '程序员'
const prevTrans = { //上一个状态可能类型
B: [E, S],
E: [B, M],
M: [B, M],
S: [E, S]
}
// 通过动态规划计算下面结果
"'程'的概率" = {B:'', E:'', M:'', S:''}
"'序'的概率" = {B:'', E:'', M:'', S:''}
"'员'的概率" = {B:'', E:'', M:'', S:''}
/*
开头字符概率是确定的,后一个要根据前一个来确定。如'序'(有BEMS)中的B,和前一个一起的可能类型组合是:EB或SB,则选择计算两种的较高概率的一种。
所以概率表保存的是组合的概率,每个字符的概率都基于前n个字符的概率。这里和cut函数最大切分组合类似,只是那里是从后往前,这里是从前往后的*/
/*
"'程'的概率" = {B:'', E:'', M:'', S:''} //保存字符'程'的不同类型的概率
"'序'的概率" = {B:'', E:'', M:'', S:''} //保存字符'程序'的不同类型的概率
"'员'的概率" = {B:'', E:'', M:'', S:''} //保存字符'程序员'的不同类型的概率
*/
// 概率可以确定了,只要在计算概率时,同时保存路径
// 根据最后得到的最大概率类型,就可以得出是哪个路径了
path = {B: "BEB", E: "BME", M: "BMM", S: "BES"}
改成官方的方式,果然好多了,再也不怕用太多内存了
https://blog.csdn.net/daniel_ustc/article/details/48223135
用process.memoryUsage测试用掉的堆和时间
字典结构如下:
str = '北京大学'
// trie结构实现
{
'北':{
number: '',
tag:'',
bound: true,
chilren:{
'京':{...}
}
},
}
// 数组实现
[
'北': {number:17860,tag:'ns'},
'北京': {number:34488,tag:'ns'},
'北京大': 0,
'北京大学': {number:2053,tag:'nt'}
...
]
测试性能:
var one = process.memoryUsage()
var start = new Date()
var sentence = "我是拖拉机学院手扶拖拉机专业的。不用多久,我就会升职加薪,当上CEO,走上人生巅峰。";
const myjieba = new Cut()
const res = myjieba.cut(sentence);
var two = process.memoryUsage()
var usage = two.heapUsed - one.heapUsed
console.log(usage)
console.log(new Date() - start)
时间 | Trie结构 | 数组 |
---|---|---|
win10 | (127M, 1.1s) | (110M, 1s) |
Ubuntu | (117M, 1.5s) | (108M, 1.3s) |
分别用我的win10和虚拟机测试,其中的字典都是nodejieba
里面的,用到了jieba.dict.utf8
、hmm_model.utf8
和user.txt
,这里只表现相对情况。
结果可见,用数组总体比用Trie结构好一点
https://www.cnblogs.com/justinh/p/7716421.html
文本预测(代码提示):前缀一致,后面的都是可能出现的结果
test/prompt.html