AC自动机处理敏感词 php代码实现

最近公司接到一个敏感词需求,但是要通过文章去筛选敏感词。如果只通过接口去处理是性能不高。

我记得以前做的一个项目是使用AC自动机,处理敏感词的。以下是我百度的一个人写的博客。

class Node
{

    public $value; // 节点值
    public $is_end = false; // 是否为结束--是否为某个单词的结束节点
    public $childNode = array(); // 子节点
    public $fail = 0; // 失败指针
    public $failIndex = 0; // 失败数组指针
    public $trie = 0; // 结构树层级
    public $parent = false; // 父节点

// 添加孩子节点--注意:可以不为引用函数,因为PHP对象赋值本身就是引用赋值
    public function &addChildNode($value, $is_end = false)
    {
        $node = $this->searchChildNode($value);
        if (empty($node)) {
// 不存在节点,添加为子节点
            $node = new Node();
            $node->value = $value;
            $node->parent = &$this;
            $node->trie = $this->trie + 1;
            $this->childNode[] = $node;
        }
        if (!$node->is_end) {
            $node->is_end = $is_end;
        }
        return $node;
    }

// 查询子节点
    public function searchChildNode($value)
    {
        foreach ($this->childNode as $k => $v) {
            if ($v->value == $value) {
// 存在节点,返回该节点
                return $this->childNode[$k];
            }
        }
        return false;
    }
}

// 添加字符串
function addString(&$head, $str)
{
    $node = null;
    for ($i = 0; $i < strlen($str); $i++) {
        if ($str[$i] != ' ') {
            $is_end = $i != (strlen($str) - 1) ? false : true; // 如果最后一位就是false;
            if ($i == 0) {
                $node = $head->addChildNode($str[$i], $is_end);
            } else {
                $node = $node->addChildNode($str[$i], $is_end);
            }
        }
    }
}

// 获取所有字符串--递归
function getChildString($node, $str_array = array(), $str = '')
{
    if ($node->is_end == true) {
        $str_array[] = $str;
    }
    if (empty($node->childNode)) {
        return $str_array;
    } else {
        foreach ($node->childNode as $k => $v) {
            $str_array = getChildString($v, $str_array, $str . $v->value);
        }
        return $str_array;
    }
}

// 字符串多模匹配
function search($p, $head, &$failArray)
{
    $i = 0;
    $res = [];
    while ($i < strlen($p)) {
        $head = searchWords($head, $p[$i], $res, $failArray, $i);
        $i++;
    }
    return $res;
}

function searchWords(&$head, $value, &$res, &$failArray, &$i)
{
    foreach ($head->childNode as $k => $v) {
        if ($v->value == $value) {
// 成功存入
            if ($v->is_end == true) {
                $res[getWords($head->childNode[$k])][] = $i;
            }
// fail节点也是指向一个结束节点
            if ($failArray[$v->fail]->is_end == true) {
                $res[getWords($failArray[$v->fail])][] = $i;
            }
// 跳转fail
            if (empty($v->childNode)) {
                return $failArray[$v->fail];
            }
// 继续下一级匹配
            return $head->childNode[$k];
        }
    }
// fail指针正在后退,没到root节点主指针不动
    if ($head->failIndex) {
        $i--;
    }
// 失败指向fail 对比指针不动
    return $failArray[$head->fail];
}

// 获取完整字符
function getWords($node)
{
    $str = '';
    while ($node->parent) {
        $str .= $node->value;
        $node = $node->parent;
    }
    return strrev($str);
}

// 构造fail指针
function buildFailIndex(&$node, $fail_array = [], &$failTrie)
{
    $fail_array[] = $node;
    if (!isset($failTrie[$node->trie])) {
        $failTrie[] = [];
    }
    ($failTrie[$node->trie])[] = &$node;
    $node->failIndex = count($fail_array) - 1;
    if (empty($node->childNode)) {
        return $fail_array;
    } else {
        foreach ($node->childNode as $k => $v) {
            $fail_array = buildFailIndex($node->childNode[$k], $fail_array, $failTrie);
        }
        return $fail_array;
    }
}

// 结构树
function trie(&$failArray, &$failTrie)
{
    foreach ($failTrie as $k => $v) {
// 层数循环
        foreach ($v as $k1 => $v1) {
// 每层每个节点循环
            $failTrie[$k][$k1]->fail = buildTrie($failTrie[$k][$k1], $failArray);
        }

    }
}

function buildTrie(&$node, &$failArray)
{
    if ($node->failIndex == 0 || $node->parent->failIndex === 0) {
        return 0;
    }
// 循环问题
    foreach ($failArray[$node->parent->fail]->childNode as $k => $v) {
        if ($v->value == $node->value) {
            return $v->failIndex;
        }
    }
    return 0;
}

/* 调用测试开始 */
$head = new Node;

// 添加单词
addString($head, 'say');
addString($head, 'she');
addString($head, 'sher');
addString($head, 'h');
addString($head, 'her');
// fail二维指针数组(方便遍历)
$failTrie = [];
// 创建一个fail指针数组
$failArray = buildFailIndex($head, [], $failTrie);
trie($failArray, $failTrie);
var_dump(search('hshershrewr', $head, $failArray));

 

 

摘自 https://blog.csdn.net/weixin_33877092/article/details/91388544?utm_medium=distribute.pc_relevant.none-task-blog-baidujs-2

posted @ 2021-01-31 14:05  GreenForestQuan  阅读(112)  评论(0编辑  收藏  举报