关键字过滤算法

本故事根据现实经历改造,数据等比例缩放,并非真实数据。故事内容纯属虚构,如有雷同,纯属巧合。

今天的大整顿也太缺德了点,竟然在下午3点多收到信说要明天下午5点之前做好关键字过滤工作。天啊!自己假设一个小网站闹一下,虽然小打小闹,也有十几万篇文章。要是人工审核肯定要疯了——就我一个人不吃不喝不拉不撒不睡觉,连续审他个24小时,假设是15W篇,那就需要平均每秒审1.74篇。疯了……

之前也没有管这些,只好临时抱佛脚,赶紧写一个小程序对关键字做过滤。对方发了一个关键字的文本文件,根据这些文字作过滤就OK了。(顺便骂一句,制作这个文本的简直不是人,一大堆的重复数据不说,格式乱七八糟简直要人命,光整理这个就花了我1个小时。)

OK,说干就干,这个时候再到网上找什么算法是来不及了,只好自己来做。

Version1:
string[] xxxList = GetList();
foreach(Article article in GetAllArticles())
{
  foreach(string xxxWord in xxxList)
  {
    if (article.Text.Contains(xxxWord))
    {
      article.NeedManualAudit = true;
      article.Update();
      break;
    }
  }
}

不管了,先运行一下,能查多少先查多少吧。再说了,我还要整理一下那个该死的关键字列表。
十分钟后一看,乖乖龙地动!平均超过一分钟才搜索完一篇文章……比亲自上阵还要糟糕!
稍微分析了一下,发现如下几个问题:
1、关键词文件内容混乱,大量出现重复的关键词,造成无谓的重复运算;
2、关键词挑选方式太垃圾了,比如南方xxx、北方xxx、西方xxx、东方xxx,其实只要有xxx就应该要标记上,更可气的是,这个关键词列表里面甚至就有xxx这个词,那么那一大堆的南方xxx都不知道有什么存在的必要;
3、关键词数量巨大,完全不重复的关键词超过2k个条目,即使提炼关键字之后,还有将近700个关键字。
搞清楚了问题所在,基本上就可以开始动手了。

Version2:
string[] xxxList = GetList();
foreach(Article article in GetAllArticles())
{
  foreach(string xxxWord in xxxList)
  {
    if (article.Text.Contains(xxxWord))
    {
      article.NeedManualAudit = true;
      article.Update();
      break;
    }
  }
}

嗯,怎么还是一样的程序?没错,我想程序跑着再慢,也还是跑着。所以花了时间修完关键字列表之后,就用这个程序来跑。有一句话,叫做最简单的就是最美的。虽然很多时候这句话不准确,但是有的时候还是对的。比如说,你想了一个很复杂的方案,说不定你写错了就翘翘了——指不定需要花多少个Hour来拍查你的bug。这个程序虽然看着简陋,而且估计性能有问题。但怎么讲这个程序肯定是理论上正确的……

盯着程序跑了3分钟之后,发现还是不理想。总结了一下原因:
1、程序没有改变,不可能期待有质的飞跃,这个程序的时间复杂度估计应该是O(NL),即取决于关键字条目数量N和文本长度L的乘积;
2、关键字整理之后,虽然理论上关键字数量是少了,但是原来有一些关键字堆在一块,比如xxxAAAbbbCCCCddd,实际上每一个都是关键字。手动修了关键字列表之后,程序能识别出来的关键字反而增加了,扣掉重复的多余的关键字,实际上没有少多少关键字。

那怎么办好呢?已经半夜了,没时间去思考太多的东西了,最明显能够加快速度的,就是把时间复杂度从O(NL)降低到O(L),或者接近这个O(L)也可以。幸好回家路上已经有一些构思,于是

Version 3:
string[] xxxList = GetList();
List<string>[] xxxListIndexed = new List<string>[65536]; // 内存大就是好啊……386时代用QB写程序可不能这么写
// 下面这个是提速的关键,就是建立一个简单的首字符索引。
// 反正一个Unicode最多就是0~65535,所以前面直接建立了这么一个数组。
foreach(string item in xxxList)
{
   List<string> xlist = xxxListIndexed[(ushort) item[0]]
   if (xlist == null)
  {
     xlist = new List<string>();
     xxxListIndexed[(ushort) item[0]] = xlist;
  }
  xlist.Add(item);
}
// 对每一个文章做搜索
foreach(Article article in GetAllArticles())
{
  string text = article.Text;
  ScanArticle(article);
}

/*** ScanArticle函数 ***/
for (int i=0; i<text.Length; i++)
{
  // 看看当前字符是否存在首字符列表当中,如果有,在搜索首字符是这个字符的那堆关键字。
  List<string> foundKeys = xxxListIndexed[(ushort) item[0]];
  if (foundKeys != null)
  {
    foreach(string key in foundKeys)
    {
       if (text.IndexOf(key, i, 20) >=0)
      {
         article.NeedManualAudit = true;
         article.Update();
         return;
      }
    }
  }
}

这一改不得了,基本上接近于O(L)的速度了,于是立即变成每分钟扫描大概50多篇文章。其实按道理来讲,还有更加快速的算法,比如说前面那个给他作成二叉树,或者结合KMP的思想弄一下。但是实在是没有时间去做这个优化,要检索的文章数量也不大,估计优化完之前就已经检索完了。尽管如此,我还是挑了一个简单的办法去优化。虽然我说是这里面最简单的,也够复杂了,弄了一大堆的ManualResetEvent,主要是为了一些不可告人的目的(其实是说出来太复杂罗嗦了)。这个Version4就不贴出来了,主要的改进就是用了多线程,服务器有两块双核CPU,不利用上太可惜了,用上之后Version4的速度大概每分钟150篇。

到这里还没有完,这个时候感觉整个服务器的性能都已经被压榨光了——却不平衡。其实我这里用了两台服务器,前端Web一台,后端DB一台。发现DB端的CPU全满,服务端的CPU懒懒散散。赶紧分析了一下SQL语句,结果发现是索引的问题。按照分析器的建议给做了一下索引,结果DB服务器的CPU占用量就猛降一半。而且这个时候速度也更快了,达到每分钟600篇左右。

又三个小时过去了,天终于亮了,所有文章也终于搜索完毕了。辛辛苦苦熬了一晚上,对所有的工作作了一个总结:
1、鞋不在花,合脚就行
2、优化系统,综合才灵
3、斯是陋室,惟吾德馨……不是有句话么:人品问题……幸好人品好,总算按时搞定。

事后我又上网搜索了一下,没有发现什么简单的关键词/关键字过滤算法。一搜出来都是给Google百度之类用的重量级网页搜索算法,如果不是网页搜索的,就是要弄什么词干分析,中文分词,或者神经网络的。要我24小时能把这些东西堆上去,那估计我要么是这一行业的老大了,要么我就真是神经了。

大家为了对付那些所谓关键字屏蔽/过滤/审核之类的事情,有什么自己的算法或者心得体会呢?我这个简陋的算法,算是抛砖引玉吧。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值