艾伟:一个简单的关键字过滤算法

简介: 早上看到老赵的《一个较完整的关键字过滤解决方案(上)》文章,讲到怎样在项目中嵌入过滤方案的问题,以及提到 xingd 和 sumtec 两位大师发表的系列互拼的文章,在此我也忍不住谈谈自己遇到的问题以及一个的简化版的算法。

早上看到老赵的《一个较完整的关键字过滤解决方案(上)》文章,讲到怎样在项目中嵌入过滤方案的问题,以及提到 xingdsumtec 两位大师发表的系列互拼的文章,在此我也忍不住谈谈自己遇到的问题以及一个的简化版的算法。

因为过滤关键字机制到处可见,于是聪明的网友就会想到各种各样的方法突破,例如:

1、中文会用繁体字的方法避开关键字扫描
2、在关键字中间插入无意思的特殊字符,例如 * & # @ 等,而且个数可变
3、使用谐音或拆字法变换关键字

在实现自己的算法时也有些问题:

4、随着时间推移,关键字列表会越来越大,有些论坛常用的正则表达式N次扫描的方法显得效率很低。
5、关键字有不同的严重级别,有些需要禁止,有些只需要替换,还有一些可能记录一下即可。


针对这些问题,可采用的应对方法:

1、加载关键字列表时,将所有的关键字转换成繁体字一份,以扫描繁体版的关键字;
这个转换工作只需一句就可以实现了:
s=Microsoft.VisualBasic.Strings.StrConv(word, Microsoft.VisualBasic.VbStrConv.TraditionalChinese, 0);

2、在扫描原文本时,如果遇到关键字的首个文字,忽略其后的特殊字符,直到下一个有意义的文字为止,当然这里需要在定义关键字列表时指定哪些才需要这样扫描,并不是所有关键字都采用这种方式;
例如有关键字 “你好”经常会被人输入成“你x好”或者“你xxxxx好”,那么在关键字列表里就需要定义成“你*好”,在匹配关键字时,如果遇到星号就忽略原文本下一个为特殊的字符。

3、遇到谐音和拆字时,没什么好办法了,只好将这些谐音词和拆分词也加入到关键字列表。

4、不用正则表达式或者 String.IndexOf方法,可以将所有关键字的首字相同的组成一个一个小组,然后在将首字放到一个散列表(HashTable/Dictionary),在扫描原文本时先在散列表里扫描,如果碰到了首字再扫描同组的关键字,这样简单处理一下效率可以提高很多。

还有一个比用散列表更好的方法,将散列表改成一个大小为char.MaxValue的数组,然后将首个文字转成int,即char->int,然后将关键词集合放到相应下标里。这样在扫描原文本时,将被扫描的字符转成int,然后试探数组相应下标的元素是否不为NULL。这样比用散列表会更快一些。

5、在定义关键字时,同时给一个“级别”属性,例如使用 E,R,B分别表示只记录、替换、禁止等情况。
于是关键字的列表如下所示:
你滚 E
他niang的 R
成*人*网*站 B

这里贴一下关键的部分代码:

Code
private WordGroup[] _wordTable;

public FilterResult Filter(ref string source,char replaceChar)
{
    
//NOTE:: 
    
//  如果方法返回  FilterResult.Replace或者FilterResult.Banned,则原字符串的某些字会被替代为星号,替代后的字符串可以由source取得

    
if (String.IsNullOrEmpty(source)) return FilterResult.Pass;

    FilterResult result 
= FilterResult.Pass;
    
char[] tempString = null;

    
int start = 0;
    
for (; start < source.Length; start++)
    {
        WordGroup fw 
= _wordTable[fastToLower(source[start])];
        
if (fw != null)
        {
            
for (int idx = 0; idx < fw.Count; idx++)
            {
                WordEntity we 
= fw.GetItem(idx);
                
int matchLength=0;
                
if (we.Word.Length==0 || checkString(source, we.Word, start + 1out matchLength))
                {
                    FilterResult fr 
= we.HandleType;
                    
if (fr > result) result = fr; //记录最高级别的处理方法
                    if (fr == FilterResult.Replace || fr == FilterResult.Banned)
                    {
                        
//替换关键字
                        if(tempString==null) tempString =source.ToCharArray();;
                        
for (int pos = 0; pos < matchLength + 1; pos++)
                        {
                            tempString[pos 
+ start] = replaceChar;
                        }
                    }
                }
            }
        }
    }

    
if (result > FilterResult.RecordOnly)
    {
        source 
= new string(tempString);
    }

    
return result;
}

private bool checkString(string source, string keyword, int sourceStart, out int matchLength)
{
    
bool found = false;
    
int sourceOffset = 0;
    
int keyIndex = 0;
    
for (; keyIndex < keyword.Length; keyIndex++)
    {
        
if (sourceOffset + sourceStart >= source.Length) break//原始字符串已经全部搜索完毕

        
if (keyword[keyIndex] == '*')
        {
            
//跳过可忽略的字符
            while (sourceOffset + sourceStart < source.Length)
            {
                
if (isIgnorableCharacter_CN(source[sourceOffset + sourceStart]))
                    sourceOffset
++;
                
else
                    
break;
            }
        }
        
else
        {
            
//比较字母
            if (fastToLower(source[sourceOffset + sourceStart]) == (int)keyword[keyIndex])
            {
                
if (keyIndex == keyword.Length - 1)
                {
                    found 
= true;
                    
break;
                }
            }
            
else
            {
                
break;
            }

            sourceOffset
++;//移动原始字符串
        }
    }

    
//如果匹配中关键字,则返回原字符串中被匹配中的文字的长度,否则返回0
    matchLength = sourceOffset + 1;
    
return found;
}

private int fastToLower(char character)
{
    
//将大写英文字母以及全/半角的英文字母转化为小写字母
    int charVal = (int)character;
    
if (charVal <= 90)
    {
        
if (charVal >= 65//字母A-Z
            return charVal - 65 + 97;
    }
    
else if (charVal >= 65313)
    {
        
if (charVal <= 65338)
            
return charVal - 65313 + 97//全角大写A-Z
        else if (charVal >= 65345 && charVal <= 65370)
            
return charVal - 65345 + 97//全角小写a-z
    }
    
return charVal;
}

private bool isIgnorableCharacter_CN(char character)
{
    
//NOTE::
    
//  中文表意字符的范围 4E00-9FA5
    int charVal = (int)character;
    
return !(charVal >= 0x4e00 && charVal <= 0x9fa5);
}

// 单个过滤词条目
class WordEntity
{
    
public string Word { getset; }
    
public FilterResult HandleType { getset; }
}

// 过滤词的组
class WordGroup
{
    
//NOTE::用于装载一组具有同一个字开头的过滤词

    
private List<WordEntity> _words;

    
public WordGroup()
    {
        _words 
= new List<WordEntity>();
    }

    
public void AppendWord(string word, FilterResult handleType)
    {
        AppendWord(
new WordEntity() { Word = word, HandleType = handleType });
    }

    
public void AppendWord(WordEntity word)
    {
        _words.Add(word);
    }

    
public int Count
    {
        
get { return _words.Count; }
    }

    
public WordEntity GetItem(int index)
    {
        
return _words[index];
    }
}

 

完整的源代码可以在这里下载:

http://www.uushare.com/user/csprogram/file/1132054

源代码可能会有bug或者效率低的地方,望感兴趣的朋友请帮忙补充和改进一下。

目录
相关文章
|
负载均衡 监控 网络协议
深入理解并实现负载均衡技术
【5月更文挑战第23天】本文探讨了负载均衡技术,旨在应对互联网高并发需求。负载均衡通过分散请求至多台服务器,提升系统性能和可靠性。核心是负载均衡器,其工作流程包括接收请求、解析、选择服务器、转发及返回响应。负载均衡技术分类包括反向代理(如Nginx、HAProxy)、DNS、IP(如LVS)和应用层负载均衡。实现时,以Nginx为例,需安装、配置反向代理、分发策略并启动服务。监控和优化是持续过程。负载均衡技术将持续发展,适应云计算和大数据时代。
|
移动开发 JavaScript 前端开发
HTML5 Audio(音频)详解
HTML5 通过 `&lt;audio&gt;` 标签简化了网页音频嵌入。本文详细介绍其基本语法与常用属性(如 `controls`、`autoplay`),并通过示例代码展示如何使用 JavaScript 控制音频播放及处理音频事件。此外,还提供了关于浏览器兼容性、自适应设计及无障碍访问的注意事项,助您优化音频体验。
|
机器学习/深度学习 人工智能 测试技术
VisionTS:基于时间序列的图形构建高性能时间序列预测模型,利用图像信息进行时间序列预测
构建预训练时间序列模型的主要挑战在于获取高质量、多样化的时间序列数据。目前有两种方法:迁移学习LLM(如GPT-4或Llama)和从零训练。尽管迁移学习可行,但效果有限;从零训练则依赖大量数据,如MOIRAI、TimesFM和TTM等模型所示。为解决这一难题,研究人员提出利用图像数据进行时间序列预测。
885 11
VisionTS:基于时间序列的图形构建高性能时间序列预测模型,利用图像信息进行时间序列预测
|
存储 数据挖掘 大数据
湖仓一体全面开启实时化时代
本文整理自阿里云开源大数据平台负责人王峰(莫问)老师在5月16日 Streaming Lakehouse Meetup · Online 上的分享,主要介绍在新一代湖仓架构上如何进行实时化大数据分析。
51121 12
湖仓一体全面开启实时化时代
|
存储 算法 C++
【C++】vector介绍以及模拟实现(超级详细)
【C++】vector介绍以及模拟实现(超级详细)
250 4
|
安全 关系型数据库 Go
远程连接PostgreSQL:配置指南与安全建议
远程连接PostgreSQL:配置指南与安全建议
1231 0
|
存储 Java 开发者
使用Spring Boot 3.3全新特性CDS,启动速度狂飙100%!
【8月更文挑战第30天】在快速迭代的软件开发周期中,应用的启动速度是开发者不可忽视的一个重要指标。它不仅影响着开发效率,还直接关系到用户体验。随着Spring Boot 3.3的发布,其中引入的Class Data Sharing(CDS)技术为应用的启动速度带来了革命性的提升。本文将围绕这一全新特性,深入探讨其原理、使用方法以及带来的实际效益,为开发者们带来一场技术盛宴。
966 2
|
存储 编译器 Swift
Swift笔记:Swift中的扩展语法
Swift笔记:Swift中的扩展语法
412 1
|
存储 负载均衡 网络协议
后端开发中的服务发现
【2月更文挑战第5天】在后端开发中,服务发现是至关重要的一个环节。本文将介绍服务发现的概念和原理,并提供一些实际应用场景以及相关技术解决方案。
|
存储 运维 监控
链路追踪(Tracing)其实很简单——分布式链路追踪的应用与兴起
作者:夏明(涯海) 创作日期:2022-07-14 专栏地址:【稳定大于一切】【稳定大于一切】分布式链路追踪已经被广泛应用于中大型企业的 IT 运维领域,为分布式应用的性能诊断与稳定性保障提供了有效的帮助。此外,分布式链路追踪的开源和商业化生态也发展迅猛,大量独立服务商或云厂商纷纷跟进,共同推动了分...
724 0
链路追踪(Tracing)其实很简单——分布式链路追踪的应用与兴起