相关概念可以看 https://blog.csdn.net/txl199106/article/details/45315703
网上随便都有了。
另外在看之前请一定要先弄清楚Trie树和KMP匹配算法
AC自动机最大的好处是可以多屏蔽词同时匹配。(如果我没理解错的话)
但是不支持混淆匹配,例如 屏蔽词 AB,用户输入AXXXXB。这个时候不能进行混淆匹配
项目的另一套DFA算法是支持混淆匹配,但是不支持多屏蔽词同时匹配。
不知道有没有大佬能有同时支持2种的解决方案。
其他的算法分析我就不说了,网上一大把,但是实现都是C++的。
因为游戏业务需要,所以写了一套lua的AC,匹配写的有点糟糕,也掺杂了很多业务逻辑。希望大佬看到不要笑话。
上代码:
wordList是配置,按道理直接COPY过去就可以用,自己传配置列表进去
目前自己跑了一遍脚本,多词匹配貌似可以,但是没有大量随机验证,希望有碰到问题的可以留言。
没有做重复词的逻辑优化,如果有时间可能会加入,因为是前端屏蔽,所以对性能要求没有那么苛刻。
--字符串转换为字符数组 --注入string table里面 function string.toCharArray(str) str = str or "" local array = {} local len = string.len(str) while str do local fontUTF = string.byte(str,1) if fontUTF == nil then break end --lua中字符占1byte,中文占3byte if fontUTF > 127 then local tmp = string.sub(str,1,3) table.insert(array,tmp) str = string.sub(str,4,len) else local tmp = string.sub(str,1,1) table.insert(array,tmp) str = string.sub(str,2,len) end end return array end local ACAutoMachine = {} ACAutoMachine.Trie = { char = "root", index = 1, isEnd = false, nodeDic = {}, faild= nil, } local worldList = {} local queue = {} --创建trie树 function ACAutoMachine.creatTree(wordList) worldList = wordList for i,word in ipairs(wordList) do ACAutoMachine.creatBranchNode(ACAutoMachine.Trie,string.toCharArray(word)) end --构造失败指针 ACAutoMachine.buildNodeFaild(ACAutoMachine.Trie) end function ACAutoMachine.pushQueue(node) table.insert( queue, node) end function ACAutoMachine.popQueue() local tb = queue[1] table.remove(queue,1) return tb end --trie树分支的构建 function ACAutoMachine.creatBranchNode(root,charArry) local index = 1; local curNode = root local addressIndex = 1 while index <= #charArry do local char = charArry[index] if curNode.nodeDic[char] ~= nil then -- 分支里面有这个字符,不做存储操作,指针指向这个字符node。 curNode = curNode.nodeDic[char] if not curNode.isEnd then curNode.isEnd = index == #charArry end else local _node = {} _node.char = char _node.index = index if not _node.isEnd then _node.isEnd = index == #charArry --如果已经是结束字符,那么就不做处理,上同 end _node.nodeDic = {} _node.faild = {} curNode.nodeDic[char] = _node curNode = curNode.nodeDic[char] end index = index + 1 end end function ACAutoMachine.buildNodeFaild(root) root.faild = nil ACAutoMachine.pushQueue(root) local parent = {} local tmp_faild = {} while #queue > 0 do parent = ACAutoMachine.popQueue() for char,node in pairs(parent.nodeDic) do if parent.char == "root" then --第一层的 node.faild = parent else tmp_faild = parent.faild --第一次进这里面,parent是第一层的,那么faild就是root while tmp_faild do --第一次 root 之后 有可能是root 有可能是某个node if tmp_faild.nodeDic[char] then --在第二层里面找 node.faild = tmp_faild.nodeDic[char] --第一种情况,回溯的父节点的faild里面有,那么条件成立 break; end tmp_faild = tmp_faild.fail if not tmp_faild then --第一次,必然是空,于是指向root指针 node.faild = root end end end ACAutoMachine.pushQueue(node) end end end
function ACAutoMachine.query(arry) local p = ACAutoMachine.Trie local len = #arry local indexList = {} local tempList = {} for i=1,len do local x = arry[i] --如果p找不到,并且p不是root while (not p.nodeDic[x]) and p.faild do p = p.faild --准备找P的失败节点 if not p.faild then --找到根节点都没找到,代表之前的查找已经失败,不能完全匹配。清空标记列表 tempList = {} end end p = p.nodeDic[x] --开始寻找 if not p then --还是找不到,指向root p = ACAutoMachine.Trie else table.insert( tempList, i) end if p.isEnd then --某一组关键词已经完全匹配 for i,index in ipairs(tempList) do --加入真实匹配列表 table.insert(indexList, index) end tempList = {}--清空临时列表,继续找下一个关键字 end end return indexList end function ACAutoMachine.replace(str,mark) mark = mark or "*" local arry = str:toCharArray() local maskList = ACAutoMachine.query(arry) local _str = "" for i,index in ipairs(maskList) do arry[index] = mark end for i,char in ipairs(arry) do _str = _str..char end return _str end function ACAutoMachine.getMaskWorlds() return worldList end return ACAutoMachine