当前位置: 首页 > news >正文

独立游戏《星尘异变》UE5 C++程序开发日志8——实现敏感词过滤功能(AC自动机)

         在游戏中经常会有需要玩家输入一些内容的功能,例如聊天,命名等,这款游戏只有在存档时辉用到命名功能,所以这个过滤也只是一个实验性的功能,我们将使用AC自动机来实现,这是在我们把“csdn”这个词设置为屏蔽词后的效果:

目录

一、敏感词词典的处理

二、搭建AC自动机

1.自动机节点的数据机构

2.加载词典

3.建立字典树

4.建立失配指针

三、替换字符串中的敏感词


一、敏感词词典的处理

        我们是从别的地方找的开源词典,所以要做一下筛选,首先我们要去重,然后去除所有的标点符号空格和其他无关字符,然后同时去掉长度为1的字符,因为其会在AC自动机中表现的过于严格

wifstream InputTxt;
wofstream OutputTxt;
//词典的路径,这里是单独开了一个程序,所以和后面项目里相关代码用到的路径不同
InputTxt.open("Dict.txt", ios::out);
//使用宽字符串读入
wstring Word;
map<wstring, bool>Words;	
while (getline(InputTxt,Word))
{//去重if (Words.find(Word) == Words.end()){//去掉短字,但这里对中文无效,因为一个中文字长度大概率不为1if (Word.size() == 1)continue;for (auto& It1 : Word){//统一成小写if (iswupper(It1)){It1 = towlower(It1);}//去除字符				if (iswpunct(It1)||iswblank(It1)||iswspace(It1)){Word.erase(It1);It1--;}				}//记录这个词处理完毕Words[Word] = true;}
}
InputTxt.close();
OutputTxt.open("Dict.txt", ios::out);
//将处理完的词重新写入词典
for (auto& It: Words)
{OutputTxt << It.first << endl;
}

二、搭建AC自动机

        AC自动机就是在字典树的基础上加入了类似于KMP的失配指针,当匹配串在树上失配时,会回溯到某个上一层的节点,该节点的所有父节点即前缀,和失配节点的所有父节点的后缀,形成最大匹配,使多模匹配的效率达到近似O(匹配串长度)

1.自动机节点的数据机构

        因为我们要将匹配到的敏感词替换成'*',所以相比于一般的自动机节点,要在每个词的末尾记录这个词的长度,同时因为不止26个字母,所以也用红黑树替代了数组

class FSensitiveWordFilterStruct
{
public:FSensitiveWordFilterStruct()=default;explicit FSensitiveWordFilterStruct(const wchar_t&InputCharacter):Character(InputCharacter){};//字符wchar_t Character{'#'};//匹配的字符串的长度int Length{0};//子节点TMap<wchar_t,std::shared_ptr<FSensitiveWordFilterStruct>>ChildNode;//失配指针FSensitiveWordFilterStruct* FailPointer{this};
};

        然后我们在游戏实例中声明自动机的根节点:

//屏蔽词过滤器树根
std::shared_ptr <FSensitiveWordFilterStruct>SensitiveWordFilterRoot;

        在游戏启动时初始化AC自动机,用到的函数后面一个一个讲:

UAstromutateGameInstance::UAstromutateGameInstance()
{//加载词典LoadTXTFile("/Movies/Dict.txt");//实例化自动机根节点SensitiveWordFilterRoot=std::make_shared<FSensitiveWordFilterStruct>(FSensitiveWordFilterStruct());//将词典中的词添加到树上for(const auto&It:*SensitiveWords){AddWordToSensitiveWordTree(It);}//建立失配指针InitializeSensitiveWordTree();
}

2.加载词典

        这里我们把词典作为txt文件放在Movies文件夹下,因为该文件夹中的所有文件都会被原封不动的打包,我们将所有敏感词存到一个TArray中

//声明敏感词词典
TSharedPtr<TArray<FString>> SensitiveWords;
auto UAstromutateGameInstance::LoadTXTFile(const FString& Path)->void
{//获取词典路径FString Temp{FPaths::ProjectContentDir()+Path};//实例化词典数组SensitiveWords=MakeShared<TArray<FString>>(TArray<FString>());//加载所有词FFileHelper::LoadFileToStringArray(*SensitiveWords,*Temp);UE_LOG(LogTemp,Warning,TEXT("SensitiveWords loade %d Words"),SensitiveWords->Num());
}

3.建立字典树

        从根节点开始,遍历模式串,如果当前点没有当前字符对应的子节点,就创建之,然后无论有无都移动到该子节点

auto UAstromutateGameInstance::AddWordToSensitiveWordTree(const FString& InputString) const->void
{//获取根节点FSensitiveWordFilterStruct* Temp=SensitiveWordFilterRoot.get();//遍历模式串中的每一个字符for(const auto&It:InputString){wchar_t CurrentChar{It};//如果当前点没有对应的子节点,就添加之if(!Temp->ChildNode.Contains(CurrentChar)){Temp->ChildNode.Add(CurrentChar,std::make_shared<FSensitiveWordFilterStruct>(FSensitiveWordFilterStruct(CurrentChar)));}Temp=Temp->ChildNode[CurrentChar].get();}//将词的长度记录在词尾Temp->Length=InputString.Len();
}

4.建立失配指针

        因为失配指针指向的节点一定在当前点的上层,所以我们进行bfs,首先将根节点的所有直连的子节点的失配指针指向根节点,因为这些点的上层节点只有根节点。然后对于一个失配点,如果其父节点的失配指针指向的点的子节点中有和该失配点相同的点,则失配点的失配指针指向该点,否则指向根节点

auto UAstromutateGameInstance::InitializeSensitiveWordTree() const -> void
{//bfs队列std::queue<std::shared_ptr<FSensitiveWordFilterStruct>>Queue;//将深度为1的点的失配指针指向根节点for(auto&It:SensitiveWordFilterRoot->ChildNode){It.Value->FailPointer=SensitiveWordFilterRoot.get();Queue.push(std::make_shared<FSensitiveWordFilterStruct>(*It.Value));}while(!Queue.empty()){std::shared_ptr<FSensitiveWordFilterStruct> CurrentNode=Queue.front();Queue.pop();//遍历所有子节点for(auto&It:CurrentNode->ChildNode){//父节点的失配指针指向的节点是否含有匹配的子节点if(!CurrentNode->FailPointer->ChildNode.Contains(It.Key)){It.Value->FailPointer=SensitiveWordFilterRoot.get();}else{It.Value->FailPointer=CurrentNode->FailPointer->ChildNode[It.Key].get();}Queue.push(std::make_shared<FSensitiveWordFilterStruct>(*It.Value));}}
}

三、替换字符串中的敏感词

        首先我们将玩家输入的字符串使用字典中字符串同样的方法进行处理,去除符号和空格,全部转为小写,然后遍历其每一个字符,不匹配就按失配指针移动,匹配就检查是否是词尾,如果是的话根据记录的词的长度算出这个词的区间,将这个居间内的所有字符替换成'*',该操作不会影响到后面的匹配,最后将字符串还原成原来有符号和空格的格式并返回

auto UAstromutateGameInstance::ReplaceSensitiveWords(const FString& RawString)->FString
{FString Result{""};//对玩家输入的字符串进行处理for(const auto&It:RawString){if(iswpunct(It)||iswblank(It)||iswspace(It))continue;if(isupper(It))Result+=towlower(It);elseResult+=It;}FSensitiveWordFilterStruct* Temp{SensitiveWordFilterRoot.get()};//遍历匹配串的每一个字符for(int i=0;i<Result.Len();i++){wchar_t CurrentChar{Result[i]};//如果失配就一直回溯,直到根节点while(!Temp->ChildNode.Contains(CurrentChar)&&Temp!=SensitiveWordFilterRoot.get()){Temp=Temp->FailPointer;}//仍然适配就结束这个字符的搜索if(!Temp->ChildNode.Contains(CurrentChar)){Temp=SensitiveWordFilterRoot.get();continue;}//移动到匹配的节点Temp=Temp->ChildNode[CurrentChar].get();FSensitiveWordFilterStruct* Temp2{Temp};//遍历匹配到的所有词while(Temp2!=SensitiveWordFilterRoot.get()){if(Temp2->Length){//根据长度算出该词其实位置for(int j=i-Temp2->Length+1;j<=i;j++){Result[j]='*';}}Temp2=Temp2->FailPointer;}}//将处理完的字符串还原成输入的格式FString TrueResult{RawString};int CurrentIndex{0};for(auto&It:TrueResult){if(iswpunct(It)||iswblank(It)||iswspace(It))continue;if(iswupper(It)&&iswlower(Result[CurrentIndex])){continue;}It=Result[CurrentIndex++];}return TrueResult;
}

相关文章:

独立游戏《星尘异变》UE5 C++程序开发日志8——实现敏感词过滤功能(AC自动机)

在游戏中经常会有需要玩家输入一些内容的功能&#xff0c;例如聊天&#xff0c;命名等&#xff0c;这款游戏只有在存档时辉用到命名功能&#xff0c;所以这个过滤也只是一个实验性的功能&#xff0c;我们将使用AC自动机来实现&#xff0c;这是在我们把“csdn”这个词设置为屏蔽…...

使用 Swagger 在 Golang 中进行 API 文档生成

Swagger 是一款强大的 API 文档生成工具&#xff0c;可以帮助开发者轻松创建、管理和展示 RESTful API 文档。在本文中&#xff0c;我们将介绍如何在 Golang 项目中使用 Swagger 来生成 API 文档。 官网地址 &#xff1a; gin-swagger 前提条件 Golang 开发环境&#xff08;…...

Pip换源实战指南:加速你的Python开发

1. Pip换源的重要性 在使用Python进行软件开发或数据分析时&#xff0c;pip 是Python的包管理工具&#xff0c;用于安装和管理第三方库。然而&#xff0c;由于网络环境的差异&#xff0c;特别是在某些国家&#xff0c;访问默认的PyPI&#xff08;Python Package Index&#xff…...

【数据结构】常用数据结构的介绍:理解与应用

文章目录 前言一、介绍二、使用场景三、总结 前言 在计算机科学中&#xff0c;数据结构是我们组织和存储数据的方式&#xff0c;它可以帮助我们高效地执行各种操作&#xff0c;如搜索、插入和删除。从数组和链表&#xff0c;到树和图&#xff0c;不同的数据结构有着不同的优点…...

【优秀python系统毕设】基于Python flask的气象数据可视化系统设计与实现,有LSTM算法预测气温

第一章 绪论 1.1 研究背景 在当今信息爆炸的时代&#xff0c;气象数据作为重要的环境信息资源&#xff0c;扮演着关键的角色。然而&#xff0c;传统的气象数据呈现方式存在信息量庞大、难以理解的问题&#xff0c;限制了用户对气象信息的深入理解和利用。因此&#xff0c;基…...

【康复学习--LeetCode每日一题】2951. 找出峰值

题目&#xff1a; 给你一个下标从 0 开始的数组 mountain 。你的任务是找出数组 mountain 中的所有 峰值。 以数组形式返回给定数组中 峰值 的下标&#xff0c;顺序不限 。 注意&#xff1a; 峰值 是指一个严格大于其相邻元素的元素。 数组的第一个和最后一个元素 不 是峰值。…...

PYTHON学习笔记(八、字符串及的使用)

目录 1、字符串 1.1、字符串的常用操作 1.2、格式化字符串 1.2.1、占位符格式化字符串 1.2.2、f-string格式化字符串 1.2.3、str.format( )格式化字符串 1.3、数据的验证 1.4、正则表达式 1.5.1元字符 1.5.2限定符 1.5.3其他字符 1.5.4re模块 1、字符串 1.1、字符…...

文件共享功能无法使用提示错误代码0x80004005【笔记】

环境情况&#xff1a; 其他电脑可以正常访问共享端&#xff0c;但有一台电脑访问提示错误代码0x80004005。 处理检查&#xff1a; 搜索里输入“启用或关闭Windows功能”按回车键&#xff0c;在“启用或关闭Windows功能”里将“SMB 1.0/CIFS文件共享支持”勾选后&#xff08;故…...

FTP(File Transfer Protocal,文件传输协议)

文章目录 引言FTP管理工具FTP客户端FTP连接模式控制连接数据连接FTP命令/响应FTP命令FTP响应FTPSSFTP引言 FTP(File Transfer Protocal,文件传输协议)用于建立两台主机间的数据文件传输下载。使用客户/服务器(Client/Server)架构,基于TCP协议,服务端口为21。 FTP链接…...

DevEco Studio中使用Qt,编写HarmonyOS程序

文章目录 1.操作2.注意事项2.1.adapter_ts2.1.手机插到电脑后&#xff0c;DevEco无法识别 1.操作 最近需要尝试把之前在Windwos下用Qt实现的程序移植到鸿蒙&#xff08;HarmonyOS&#xff09;系统上。 我使用的DevEco版本是5.03.501 找了一下资料&#xff0c;官方&#xff0…...

基于单文档的MFC图像增强

目录 function.h ColorEnhanceDib.h ColorEnhanceDib.cpp Dib.h Dib.cpp FrequencyFilterDib.h FrequencyFilterDib.cpp GrayTransformDib.h GrayTransformDib.cpp HistogramDib.h HistogramDib.cpp SharpenProcessDib.h SharpenProcessDib.cpp SmoothProcessDib.h Sm…...

云计算实训13——DNS域名解析、ntp时间服务器配置、主从DNS配置、多区域DNS搭建

一、DNS域名解析 1.正向解析 将域名解析为IP地址 DNS正向解析核心配置 (1)安装bind [rootdns ~]# yum -y install bind (2)编辑配置文件 编辑named.conf文件&#xff0c;限定访问权限 [rootdns ~]# vim /etc/named.conf 编辑named.rfc文件&#xff0c;指定要访问的域名 [ro…...

【C#】Visual Studio2022打包依赖第三方库的winForm程序为exe

0.简介 IDE&#xff1a;VS2022 平台&#xff1a;C# .NetFramework4.7.2 WinForm界面 有GDAL、EEplus第三方库的依赖&#xff0c;所以在其他未安装环境的电脑中功能无法使用。 1. 安装 1.1 运行文件输出 在VS扩展中选择管理扩展&#xff0c;安装&#xff1a;Microsoft Visua…...

《算法笔记》总结No.11——数字处理(上)欧拉筛选

机试中存在部分涉及到较复杂数字的问题&#xff0c;这是编码的基本功&#xff0c;各位一定要得心应手。 目录 一.最大公约数和最小公倍数 1.最大公约数 2.最小公倍数 二.素数 1.判断指定数 2.输出所有素数 3.精进不休——埃拉托斯特尼筛法 4.达到更优&#xff01;——…...

DP学习——享元模式

学而时习之&#xff0c;温故而知新。 享元模式 名词解析 有必要解释下“享元”两字&#xff0c;英文原文是flyweight pattern——轻量级模式&#xff0c;但是翻译过来的“享元”两字太牛逼了——褒贬不一&#xff0c;翻译的他妈都不认识。 享元的高雅在于: 享:共享/共用 元:…...

无人机10公里WiFi图传摄像模组,飞睿智能超清远距离无线监控,智能安防新潮流

在这个科技日新月异的时代&#xff0c;我们对影像的捕捉和传播有了更高的要求。从传统的有线传输到无线WiFi图传&#xff0c;每一次技术的飞跃都为我们带来了全新的视觉体验。今天&#xff0c;我们要探讨的&#xff0c;正是一款具有划时代意义的科技产品——飞睿智能10公里WiFi…...

SAP S/4HANA Cloud Public Edition

即装即用的云ERP软件。借助SaaS模式为企业提供完备、现代化的ERP 云套件&#xff0c;为企业带来新的技术突破&#xff0c;如自动化的业务流程与基于数据的商业分析。企业可选择这款智能云ERP软件&#xff0c;快速实现自身价值。 什么是 SAP S/4HANA Cloud Public Edition&#…...

LabVIEW汽车动态信号模拟系统

随着汽车工业的快速发展&#xff0c;对汽车电子控制单元&#xff08;ECU&#xff09;的测试与仿真需求日益增加。开发了一种基于LabVIEW软件开发的汽车动态信号模拟系统&#xff0c;该系统能有效模拟ECU在实车环境下的工作状态&#xff0c;为ECU的开发和测试提供了一个高效、经…...

chrome 插件:content-script 部分逻辑在页面无法生效,可考虑插入 script 到页面上

背景: 某页面有个输入框, 用的应该是什么库里的组件, 直接修改内容不生效/机制不明确, 于是使用 paste event 粘贴到输入框, 结果发现也不行 定位: 使用 mutationObserver , 发现事件确实触发了, 输入框内容变了, 但马上又变回来了, 于是怀疑是输入框组件有做 mutationObers…...

【前端 10】初探BOM

初探BOM&#xff1a;浏览器对象模型 在JavaScript的广阔世界中&#xff0c;BOM&#xff08;Browser Object Model&#xff0c;浏览器对象模型&#xff09;扮演着举足轻重的角色。它为我们提供了一套操作浏览器窗口及其组成部分的接口&#xff0c;让我们能够通过编写JavaScript…...

设计模式和设计原则回顾

设计模式和设计原则回顾 23种设计模式是设计原则的完美体现,设计原则设计原则是设计模式的理论基石, 设计模式 在经典的设计模式分类中(如《设计模式:可复用面向对象软件的基础》一书中),总共有23种设计模式,分为三大类: 一、创建型模式(5种) 1. 单例模式(Sing…...

C++:std::is_convertible

C++标志库中提供is_convertible,可以测试一种类型是否可以转换为另一只类型: template <class From, class To> struct is_convertible; 使用举例: #include <iostream> #include <string>using namespace std;struct A { }; struct B : A { };int main…...

系统设计 --- MongoDB亿级数据查询优化策略

系统设计 --- MongoDB亿级数据查询分表策略 背景Solution --- 分表 背景 使用audit log实现Audi Trail功能 Audit Trail范围: 六个月数据量: 每秒5-7条audi log&#xff0c;共计7千万 – 1亿条数据需要实现全文检索按照时间倒序因为license问题&#xff0c;不能使用ELK只能使用…...

Nuxt.js 中的路由配置详解

Nuxt.js 通过其内置的路由系统简化了应用的路由配置&#xff0c;使得开发者可以轻松地管理页面导航和 URL 结构。路由配置主要涉及页面组件的组织、动态路由的设置以及路由元信息的配置。 自动路由生成 Nuxt.js 会根据 pages 目录下的文件结构自动生成路由配置。每个文件都会对…...

关于 WASM:1. WASM 基础原理

一、WASM 简介 1.1 WebAssembly 是什么&#xff1f; WebAssembly&#xff08;WASM&#xff09; 是一种能在现代浏览器中高效运行的二进制指令格式&#xff0c;它不是传统的编程语言&#xff0c;而是一种 低级字节码格式&#xff0c;可由高级语言&#xff08;如 C、C、Rust&am…...

实现弹窗随键盘上移居中

实现弹窗随键盘上移的核心思路 在Android中&#xff0c;可以通过监听键盘的显示和隐藏事件&#xff0c;动态调整弹窗的位置。关键点在于获取键盘高度&#xff0c;并计算剩余屏幕空间以重新定位弹窗。 // 在Activity或Fragment中设置键盘监听 val rootView findViewById<V…...

优选算法第十二讲:队列 + 宽搜 优先级队列

优选算法第十二讲&#xff1a;队列 宽搜 && 优先级队列 1.N叉树的层序遍历2.二叉树的锯齿型层序遍历3.二叉树最大宽度4.在每个树行中找最大值5.优先级队列 -- 最后一块石头的重量6.数据流中的第K大元素7.前K个高频单词8.数据流的中位数 1.N叉树的层序遍历 2.二叉树的锯…...

Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习)

Aspose.PDF 限制绕过方案&#xff1a;Java 字节码技术实战分享&#xff08;仅供学习&#xff09; 一、Aspose.PDF 简介二、说明&#xff08;⚠️仅供学习与研究使用&#xff09;三、技术流程总览四、准备工作1. 下载 Jar 包2. Maven 项目依赖配置 五、字节码修改实现代码&#…...

人工智能(大型语言模型 LLMs)对不同学科的影响以及由此产生的新学习方式

今天是关于AI如何在教学中增强学生的学习体验&#xff0c;我把重要信息标红了。人文学科的价值被低估了 ⬇️ 转型与必要性 人工智能正在深刻地改变教育&#xff0c;这并非炒作&#xff0c;而是已经发生的巨大变革。教育机构和教育者不能忽视它&#xff0c;试图简单地禁止学生使…...

RabbitMQ入门4.1.0版本(基于java、SpringBoot操作)

RabbitMQ 一、RabbitMQ概述 RabbitMQ RabbitMQ最初由LShift和CohesiveFT于2007年开发&#xff0c;后来由Pivotal Software Inc.&#xff08;现为VMware子公司&#xff09;接管。RabbitMQ 是一个开源的消息代理和队列服务器&#xff0c;用 Erlang 语言编写。广泛应用于各种分布…...