游戏屏蔽词算法——AC自动机lua版本

相关概念可以看  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
 

猜你喜欢

转载自www.cnblogs.com/leixuan111/p/12825019.html