js实现敏感词过滤算法

大半个月没有更新了,由于最近有点忙(实际上是懒)javascript

最近弄了一个用户发表评论的功能,用户上传了评论,再文章下能够看到本身的评论,但做为社会主义接班人,践行社会主义核心价值观,因此给评论敏感词过滤的功能不可少,在网上找了资料,发现已经有很是成熟的解决方案。 经常使用的方案用这么两种java

  1. 全文搜索,逐个匹配。这种听起来就不够高大上,在数据量大的状况下,会有效率问题,文末有比较
  2. DFA算法-肯定有限状态自动机 附上百科连接 肯定有限状态自动机

DFA算法介绍

DFA是一种计算模型,数据源是一个有限个集合,经过当前状态和事件来肯定下一个状态,即 状态+事件=下一状态,由此逐步构建一个有向图,其中的节点就是状态,因此在DFA算法中只有查找和判断,没有复杂的计算,从而提升算法效率算法

参考文章 Java实现敏感词过滤数据结构

实现逻辑

构造数据结构

将敏感词转换成树结构,举例敏感词有着这么几个 ['日本鬼子','日本人','日本男人'],那么数据结构以下(图片引用参考文章)测试

数据结构

每一个文字是一个节点,连续的节点组成一个词,日本人对应的就是中间的那条链,咱们可使用对象或者map来构建树,这里的栗子采用map构建节点,每一个节点中有个状态标识,用来表示当前节点是否是最后一个,每条链路必需要有个终点节点,先来看下构建节点的流程图ui

流程图

判断逻辑

先从文本的第一个字开始检查,好比你我是日本鬼子,第一个字 ,在树的第一层找不到这个节点,那么继续找第二个字,到了的时候,第一层节点找到了,那么接着下一层节点中查找,同时判断这个节点是否是结尾节点,如果结尾节点,则匹配成功了,反之继续匹配spa

代码实现

####构造数据结构.net

/** * @description * 构造敏感词map * @private * @returns */
private makeSensitiveMap(sensitiveWordList) {
    // 构造根节点
    const result = new Map();
    for (const word of sensitiveWordList) {
        let map = result;
        for (let i = 0; i < word.length; i++) {
            // 依次获取字
            const char = word.charAt(i);
            // 判断是否存在
            if (map.get(char)) {
                // 获取下一层节点
                map = map.get(char);
            } else {
                // 将当前节点设置为非结尾节点
                if (map.get('laster') === true) {
                    map.set('laster', false);
                }
                const item = new Map();
                // 新增节点默认为结尾节点
                item.set('laster', true);
                map.set(char, item);
                map = map.get(char);
            }
        }

    }
    return result;
}
复制代码

最终map结构以下code

结构

查找敏感词

/** * @description * 检查敏感词是否存在 * @private * @param {any} txt * @param {any} index * @returns */
private checkSensitiveWord(sensitiveMap, txt, index) {
    let currentMap = sensitiveMap;
    let flag = false;
    let wordNum = 0;//记录过滤
    let sensitiveWord = ''; //记录过滤出来的敏感词
    for (let i = index; i < txt.length; i++) {
        const word = txt.charAt(i);
        currentMap = currentMap.get(word);
        if (currentMap) {
            wordNum++;
            sensitiveWord += word;
            if (currentMap.get('laster') === true) {
                // 表示已到词的结尾
                flag = true;
                break;
            }
        } else {
            break;
        }
    }
    // 两字成词
    if (wordNum < 2) {
        flag = false;
    }
    return { flag, sensitiveWord };
}
/** * @description * 判断文本中是否存在敏感词 * @param {any} txt * @returns */
public filterSensitiveWord(txt, sensitiveMap) {
    let matchResult = { flag: false, sensitiveWord: '' };
    // 过滤掉除了中文、英文、数字以外的
    const txtTrim = txt.replace(/[^\u4e00-\u9fa5\u0030-\u0039\u0061-\u007a\u0041-\u005a]+/g, '');
    for (let i = 0; i < txtTrim.length; i++) {
        matchResult = checkSensitiveWord(sensitiveMap, txtTrim, i);
        if (matchResult.flag) {
            console.log(`sensitiveWord:${matchResult.sensitiveWord}`);
            break;
        }
    }
    return matchResult;
}
复制代码

效率

为了看出DFA的效率,我作了个简单的小测试,测试的文本长度为5095个汉字,敏感词词库中有2000个敏感词,比较的算法分别为 DFA算法 和 String原生对象提供的 indexOfAPI作比较regexp

// 简单的字符串匹配-indexOf
ensitiveWords.forEach((word) => {
    if (ss.indexOf(word) !== -1) {
        console.log(word)
    }
})
复制代码

分别将两个算法执行100次,获得以下结果

比较结果

可直观看出,DFA的平均耗时是在1ms左右,最大为5ms;indexOf方式的平均耗时在9ms左右,最大为14ms,因此DFA效率上仍是很是明显有优点的。

相关文章
相关标签/搜索