关于地址分词的一点思路,一些主要代码的简要说明
本人的思路是,解析的结果存储在一个类似树状的结构中,就和DOM节点类似,用parent字段指向父级,用children字段指向子级
准备工作
CityModel 类
先构建出一个 CityModel 类 用来表示树的每一个节点 具体属性可参考下面
class CityModel{
constructor(option={}){
//编码
this.code=option.code;
//重点子城市
this.childrenNode=[];
//父级
this.parent=option.parent;
//层
this.level=option.level;
//权重
this.weights=option.weights;
//值
this.value=option.value;
//父节点code
this.parentCode=option.parentCode;
//匹配前的字符
this.matchText=option.matchText;
//匹配后的字符
this.matchedText=option.matchedText;
//匹配到的字符
this.matchToText=option.matchToText;
//当前处理序列
this.dealIndex=0;
}
}
解析过程
创建根节点
第一步 先创建一个根节点 然后以此节点向子级查找
createRoot(matchText){
const root={
parentCode:null,
level:0,
code:ROOTCODE,
matchText:this.filterInfo(matchText),
weights:100
};
return new CityModel(root)
}
// 这个函数主要是把字符串可能存在的前缀给他过滤掉,因为前缀会影响子节点的权重信息
filterInfo(){
let matchText=this.matchText||'';
let reg=/地址[::]+.*$/g;
let matchResults=matchText.match(reg);
if(matchResults){
return matchResults[0].replace(/^地址[::]+/,'')
}
return matchText
}
第二步 开始匹配
const root= createRoot(matchText);
root.match();
整体思路就是,匹配的同时,创建子节点。直至匹配结束,从上至下的匹配。
match 函数是匹配规则的核心了 下面来解析一下match函数
match(){
//先把对应层级的正则提取出来,主要是用于将单位给去掉,比如第一级别的省,第二级别的市,因为客户传入的字符串可能不含这个词
let reg=orderRegExp[this.level];
//当前要匹配的词
let matchText=this.matchText;
//如果为空,说明后面没有需要解析的字符了,直接返回即可
if(!matchText){
return this.checkWeight();
}
let parent=this.parent;
//获取当前节点,所有的子节点文本,如果获取河南省下面所有的市
let childrenValues=this.getChildrenValues();
if(childrenValues){
//当有子节点文本的时候
//将所有的子节点文本全部汇总成一个字符串,我是为了偷懒
let libraryStr=childrenValues.join(',');
if(reg){
//将当前层级的单位去掉
libraryStr=libraryStr.replace(reg,'');
}
//再转换成数组,上面几步都是为了去掉单位的,可以直接用for循环,也是一样的
const _library=libraryStr.split(',');
//判断子节点文本数量长度,如果为1,则说明,类似直辖市的感觉,上海-上海市
if(_library.length===1){
//判断当前子节点文本是不是在要匹配的字符串中的位置是否在前面 比如 上海 在中国上海市青浦区徐泾镇的位置。
//位置不能太靠后,否则会失去参考价值,比如 南阳邓州团结路xxx号重庆火锅 这种情况不能去找重庆,因为重庆没有南阳市
if(!(new RegExp(_library[0]).test(matchText)&&matchText.indexOf(_library[0])<=3)){
//如上海市青浦区,第一次匹配上海的时候,匹配后的字符串就变成了市青浦区,此时再匹配上海市就匹配不到了,可以去父级去匹配
if(parent&&new RegExp(_library[0]).test(parent.matchText)){
matchText=this.matchText=this.parent.matchText;
}
}
//根据子节点文本创建子节点,权重和父级权重保持一致
this.createChildNode(_library[0],this.weights,!new RegExp(_library[0]).test(matchText))
}else {
//如果子节点文本数量不为1,说明子节点文本数量大于1,循环一下
for (let i=0;i<_library.length;i++){
if(new RegExp(_library[i]).test(matchText)){
// 如果当前子节点文本是在要匹配的字符串中,根据位置生成权重信息
let _index=matchText.indexOf(_library[i]);
let weights=100-_index;
if(_index>3){
weights=50;
}
//根据子节点文本生成一个真正的子节点
let currentNode=this.createChildNode(_library[i],weights);
//如果子节点文本所在的索引小于等于3,说明在前面,则执行父节点加权操作
if(currentNode&&_index<=3){
currentNode.weightsAdd();
}
}else if(parent&&new RegExp(_library[i]).test(parent.matchText)){
//如果子节点文本在父级字符串中找到了,也会有一定可能性,但是只是将当前权重增加,不增加父级权重
let _index=parent.matchText.indexOf(_library[i]);
let weights=50-_index;
this.createChildNode(_library[i],weights);
}else {
//如果没找到,则将权重设为1,虽然没有找到,说不定只是省略了
//如 河南省邓州市,当匹配南阳的时候,因为省略了,但是当匹配邓州的时候,邓州的权重为100,则会增加间接增加南阳的权重
this.createChildNode(_library[i],1,true)
}
}
}
//排个序,权重大的在前面,优先进行子匹配
this.childrenNodeSort();
const childrenNode=this.childrenNode[0];
if(childrenNode&&childrenNode.weights<=1&&this.weights<=1){
//如果当子没有匹配到,自己也没匹配到,要么是这条路走不通了,要么是中间跨了两级都省略了,则直接关闭当前路径,走下一条路
this.close();
this.emitNext();
}else {
//如果有子,则尝试匹配第一个子,进行递归匹配
this.childrenNode[0].match();
}
}else if(typeof this.getChildren() ==='undefined'){
//当子节点为undefined的时候,地址库不可能一下子全部加载,所以这里主要是为动态加载做的
if(this.level>=stopLevel||index.emit('noChildren',this,this.getTopId())===0){
this.checkWeight();
}
}else {
//当没有子节点的时候
this.checkWeight();
}
}
match说明
关于匹配函数 match 中出现的一些重要的函数说明
createChildNode
关于createChildNode函数的说明,通过子节点文本创建子节点
createChildNode(matchToText,weights,noMatch=false){
let level=this.level+1;
let parent=this;
//通过文本超找完整文本
//例如 匹配到了南阳,根据字典,找出完整的字符南阳市
let fullInfo=this.getFullInfoByValue(matchToText);
//判断完整的字符是否在当前匹配的字符中,如果是,则匹配到的字符按照完整的来
//例如 匹配到了南阳,实际字符串为南阳市邓州市 则直接说明匹配到了南阳市,将邓州市作为子匹配
if(new RegExp(fullInfo.value).test(this.matchText)){
matchToText=fullInfo.value;
}
//将matchText用matchToText分割,后面的部分用于匹配
//比如 南阳邓州都司 matchToText为南阳,则当前生成的节点的matchText为邓州都司
//后续匹配南阳的子节点的时候,会将南阳所有的子节点分别去匹配邓州都司。以此类推
let matchTexts=this.matchText.split(matchToText);
let matchText=this.matchText;
if(matchTexts.length>1){
matchText=matchTexts.slice(1).join(matchToText);
}
weights=weights||0;
this.matchedText=matchText;
if(noMatch){
//如果没有匹配到,则生成的节点的matchToText为null
matchToText=null;
}
if(fullInfo){
//将完整的code,value取出来
let {code,value}=fullInfo;
//根据已有的信息生成一个子节点
let cityNode=new CityModel({
parentCode:parent.code,
code:code,
weights,
matchToText,
matchText,
level,
parent,
value
});
//将当前子节点推入childrenNode中
this.childrenNode.push(cityNode);
return cityNode
}
return false;
}
checkWeight
关于checkWeight的说明,校验权重,判断是否已经退出递归
checkWeight(){
if(this.weights<=97){
//小于97则认为当前分支正确性不高
this.close();
return this.emitNext();
}else {
//大于97则认为当前分支是正确的,可以结束了
return this.end();
}
}
emitNext
emitNext 指向器 自动前往下一个节点进行匹配
emitNext(){
//获取父级节点
const parent=this.parent;
if(parent){
//获取处理位置
let dealIndex=parent.dealIndex;
//获取父节点的子节点数量,也就是兄弟节点的长度
let length=parent.childrenNode.length;
if((dealIndex+2>length||length===0)){
//如果长度===0或者位置已经是最后一个了,则执行当前,否则执行父节点的 emitNext
if(parent.weights>=97&&parent.level>=0){
//如果父节点权重大于97,并且层级大于0
//排个序,权重大在前
parent.childrenNodeSort();
let child=parent.childrenNode[0];
if(child&&child.weights>=97){
//第一个子节点权重如果大于97,直接使用第一个子节点结束,否则父节点结束
child.end()
}else {
parent.end();
}
return true
}
return parent.emitNext();
}else {
// 将光标移动至下一个,然后进行匹配
parent.dealIndex++;
parent.childrenNode[parent.dealIndex].match();
}
}
return false
}
因个人能力有限,以上内容多有疏漏,欢迎大家指正