如何从字符串中剪切指定的单词

时间:2010-10-05 14:33:08

标签: c# algorithm

有一个禁止的单词列表(或更一般的字符串)和另一个列表,让我们说用户邮件。我想从所有邮件中删除所有被禁止的词语。

琐碎的例子:

foreach(string word in wordsList)
{
   foreach(string mail in mailList)
   {
      mail.Replace(word,String.Empty);
   }
}

如何改进此算法?


感谢您的建议。我投了几个答案,但我没有给任何答案标记,因为它更像是讨论而不是解决方案。有些人错过了带有坏词的禁词。在我的情况下,我不必费心去识别'sh1t'或类似的东西。

12 个答案:

答案 0 :(得分:5)

亵渎过滤的简单方法不起作用 - 复杂的方法在大多数情况下都不起作用。

当您获得类似“密码”的作品并且想过滤掉“屁股”时会发生什么?当一些聪明的人写'a $$'时会发生什么 - 意图仍然清晰,对吗?

请参阅How do you implement a good profanity filter?进行广泛讨论。

答案 1 :(得分:2)

通过绘制finite state machine (FSM)(或生成一个),然后一次解析输入1个字符并遍历各州,您将获得最佳性能。

您可以使用一个函数轻松地执行此操作,该函数将获取您的下一个输入字符和当前状态并返回下一个状态,您还可以在浏览邮件消息的字符时创建输出。你在纸上画FSM。

或者您可以查看Windows Workflow Foundation: State Machine Workflows

通过这种方式,您只需要一次浏览每条消息。

答案 2 :(得分:2)

您可以使用RegEx使事情变得更清洁:

var bannedWords = @"\b(this|is|the|list|of|banned|words)\b";

foreach(mail in mailList)
    var clean = Regex.Replace(mail, bannedWords, "", RegexOptions.IgnoreCase);

尽管如此,由于人们总会找到适合任何类型过滤器的方法,因此远非完美。

答案 3 :(得分:1)

从单词(word1|word2|word3|...)构造正则表达式并使用它来代替外部循环可能更快,从那以后,每个电子邮件只需要解析一次。此外,使用正则表达式可以使用单词边界标记(\b(word1|word2|word3|...)\b)仅删除“完整单词”。

一般情况下,我认为您不会找到比当前速度快几个数量级的解决方案:您必须遍历所有邮件,而必须搜索所有单词,没有简单的方法。

答案 4 :(得分:1)

一般算法是:

  1. 根据输入字符串生成标记列表(即将空格视为标记分隔符)
  2. 将每个标记与禁止字词列表进行比较
  3. 替换匹配的令牌
  4. 正则表达式便于识别令牌,而HashSet可以为您的禁止词列表提供快速查找。 Replace类上有一个带有函数的重载Regex方法,您可以根据查找控制替换行为。

    HashSet<string> BannedWords = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase)
    {
        "bad",
    };
    
    string Input = "this is some bad text.";
    
    string Output = Regex.Replace(Input, @"\b\w+\b", (Match m) => BannedWords.Contains(m.Value) ? new string('x', m.Value.Length) : m.Value);
    

答案 5 :(得分:1)

*替换它是令人讨厌的,但是比通过删除单词并留下格式错误的句子来消除你的意图背景的东西更不烦人。在讨论黑斯廷斯之战时,如果我看到威廉给出了标题“Grand ******* of Normandy",我会感到恼火,但至少我知道我在小孩子的游乐场玩,而他的头衔是”诺曼底大战“看起来像是一个错误,或者(更糟)我可能认为这实际上是他的头衔。”

除非有趣,否则不要尝试用更无害的单词替换单词。人们在4chan上开玩笑,但是关于历史的雅虎团体让人感到困惑,因为当eval(不是亵渎,但用于雅虎遭受过的一些XSS攻击)被中世纪的评论所取代时,正在讨论medireview和mediareview时期和中世纪(显然,medireview是美国媒体评论的拼写!)。

答案 6 :(得分:1)

在某些情况下可以改善它: 只是为了好玩:

你可以使用SortedList,如果你的邮件列表是邮件列表(因为你有一个像“;”这样的分隔符)你可以做如下:

首先计算你的运行时算法: 单词:n项。 (每个项目的长度为O(1)。 邮件列表:K项目。 邮件列表中每个项目的平均长度为Z. 邮件列表中每个子项的平均长度为Y,因此邮件列表项中的子项平均数为m = Z / Y.

你的算法需要O(n * K * Z)。 //使用knut算法的最佳方法

1.如果你在O(n log n)中对单词列表进行排序。

2.1-对每个邮件列表项使用mailingListItem.Split(“;”。ToCharArray()):O(Z)。 2.2-对邮件列表中的项目进行排序:O(m * log m) 相对于(m logm <&lt; Z),总排序在值得的情况下取O(K * Z)。

3-使用合并算法合并坏词和特定邮件列表的项目:O((m + n)* k)

总时间为O((m + n)* K + m * Z + n ^ 2),相对于m <&lt; n,总算法运行时间在值得的情况下是O(n ^ 2 + Z * K),如果n

因此,如果表现非常非常重要,你可以这样做。

答案 7 :(得分:0)

您可以考虑使用Regex而不是简单的字符串匹配,以避免替换单词中的部分内容。正则表达式允许您确保只获得匹配的完整单词。你可以使用这样的模式:

"\bBADWORD\b"

此外,您可能希望迭代外部的mailList和内部循环上的单词列表。

答案 8 :(得分:0)

通过将所有字符更改为*或其他内容来简单地编辑它们会不会更容易(也更有效率)?这样就不需要调整大型字符串的大小或移动,并且使配方更加清楚发生了什么,而不是使用缺少单词的无意义句子。

答案 9 :(得分:0)

好吧,你当然不想让天真的string.Replace()犯下这个错误。正则表达式解决方案可以工作,虽然你要么迭代或使用管道交流发电机(我不知道是否/多少会减慢你的操作速度,特别是对于大量禁止的单词)。你可以随时......不要这样做,因为无论如何都是徒劳的 - 即使没有使用确切的字母,也有办法让你的意图很清楚。

那就是说,首先要有一个“让人觉得冒犯”的单词列表是荒谬的。几乎任何一个词都会被冒犯的人

/审查是废话咆哮

答案 10 :(得分:0)

我假设您只想检测完整的单词(由非字母字符分隔)并忽略带有过滤词子串的单词(如p [ass]单词示例)。在这种情况下,你应该自己构建一个过滤词的HashSet,扫描文本中的单词,并为每个单词检查它在HashSet中的存在。如果它是一个过滤词,那么在没有它的情况下构建生成的StringBuilder对象(或者使用相同数量的星号)。

答案 11 :(得分:0)

使用this algorithm on codeproject.com比使用强力文本替换更好。