独立游戏《星尘异变》UE5 C++程序开发日志8——实现敏感词过滤功能(AC自动机)
创始人
2024-12-18 21:34:11
0

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

目录

一、敏感词词典的处理

二、搭建AC自动机

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

2.加载词典

3.建立字典树

4.建立失配指针

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


一、敏感词词典的处理

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

wifstream InputTxt; wofstream OutputTxt; //词典的路径,这里是单独开了一个程序,所以和后面项目里相关代码用到的路径不同 InputTxt.open("Dict.txt", ios::out); //使用宽字符串读入 wstring Word; mapWords;	 while (getline(InputTxt,Word)) {     //去重 	if (Words.find(Word) == Words.end()) 	{         //去掉短字,但这里对中文无效,因为一个中文字长度大概率不为1 		if (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>ChildNode;  	//失配指针 	FSensitiveWordFilterStruct* FailPointer{this}; };

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

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

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

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

2.加载词典

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

//声明敏感词词典 TSharedPtr> SensitiveWords; auto UAstromutateGameInstance::LoadTXTFile(const FString& Path)->void {     //获取词典路径 	FString Temp{FPaths::ProjectContentDir()+Path};     //实例化词典数组 	SensitiveWords=MakeShared>(TArray());     //加载所有词 	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(CurrentChar))); 		} 		Temp=Temp->ChildNode[CurrentChar].get(); 	}     //将词的长度记录在词尾 	Temp->Length=InputString.Len(); }

4.建立失配指针

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

auto UAstromutateGameInstance::InitializeSensitiveWordTree() const -> void {     //bfs队列 	std::queue>Queue;     //将深度为1的点的失配指针指向根节点 	for(auto&It:SensitiveWordFilterRoot->ChildNode) 	{ 		It.Value->FailPointer=SensitiveWordFilterRoot.get(); 		Queue.push(std::make_shared(*It.Value)); 	} 	while(!Queue.empty()) 	{ 		std::shared_ptr 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(*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); 		else 			Result+=It; 	} 	FSensitiveWordFilterStruct* Temp{SensitiveWordFilterRoot.get()};     //遍历匹配串的每一个字符 	for(int i=0;iChildNode.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; }

相关内容

热门资讯

今年以来!老k麻将有挂吗,家乡... 今年以来!老k麻将有挂吗,家乡大二辅助,扑克教程(总是存在有挂)-哔哩哔哩老k麻将有挂吗辅助器中分为...
在玩家背景下!丫丫陕西打锅子辅... 在玩家背景下!丫丫陕西打锅子辅助,微信小程序雀神挂件,微扑克教程(切实真的有挂)-哔哩哔哩1、微信小...
现有关情况通报如下!同城游破解... 现有关情况通报如下!同城游破解版下载,海盗来了辅助器无限炮,攻略教程(一直真的有挂)-哔哩哔哩1、该...
这一现象值得深思!潮汕掌上辅助... 这一现象值得深思!潮汕掌上辅助挂定制交易平台,兴动互娱辅助工具,透明挂教程(切实真的是有挂)-哔哩哔...
连日来!新518互游脚本下载,... 连日来!新518互游脚本下载,多乐辅助,微扑克教程(好像真的有挂)-哔哩哔哩运新518互游脚本下载辅...
近日!四川辅助工具,潮汕掌上娱... 近日!四川辅助工具,潮汕掌上娱有破解版吗,德州教程(其实有挂)-哔哩哔哩1、近日!四川辅助工具,潮汕...
今天下午!大唐撸麻雀辅助码,福... 今天下午!大唐撸麻雀辅助码,福建天天开心辅助软件大全,科技教程(本来是有挂)-哔哩哔哩1、打开软件启...
据目击者称!福州十八扑外卦,新... 据目击者称!福州十八扑外卦,新畅游互娱辅助,技巧教程(确实是有挂)-哔哩哔哩新畅游互娱辅助是一种具有...
出乎意料的是!欢聚水鱼智能辅助... 出乎意料的是!欢聚水鱼智能辅助教程,博雅红河西元红河挂,教你教程(都是有挂)-哔哩哔哩欢聚水鱼智能辅...
最终!约战竞技场辅助器,邳州友... 最终!约战竞技场辅助器,邳州友友有没有辅助软件,wpk教程(都是真的有挂)-哔哩哔哩1、每一步都需要...