【问题标题】:Best approach of word censoring - C# 4.0文字审查的最佳方法 - C# 4.0
【发布时间】:2014-02-27 23:12:00
【问题描述】:

对于我定制的聊天屏幕,我使用下面的代码来检查被审查的词。但我想知道这个代码性能能提高吗。谢谢。

    if (srMessageTemp.IndexOf(" censored1 ") != -1)
        return;
    if (srMessageTemp.IndexOf(" censored2 ") != -1)
        return;
    if (srMessageTemp.IndexOf(" censored3 ") != -1)
        return;

C# 4.0 。实际上列表要长得多,但我不会放在这里,因为它会消失。

【问题讨论】:

  • 当你找到这些字的时候你会怎么做?顺便说一句,使用带有单词边界的 RegEx 更准确,因为您的方法不会找到“蔓越莓”。如果您想替换单词,这可能会有所帮助:stackoverflow.com/questions/3342011/….
  • 用户可以通过@ys f|找到绕过你的控制的方法
  • L.B 这是非常正确的,但至少这有帮助 :D chibacity 我要去看看。

标签: c# keyword


【解决方案1】:

【讨论】:

  • 你好。这似乎是非常不错的解决方案。这会更快吗?,
【解决方案2】:

你可以简化它。这里 listOfCencoredWords 将包含所有被审查的词

 if (listOfCensoredWords.Any(item => srMessageTemp.Contains(item)))
     return;

【讨论】:

  • 这不会限制自己的单词边界,它会重新搜索每个单词的消息字符串。
【解决方案3】:

如果你想让它变得非常快,你可以使用 Aho-Corasick 自动机。这就是防病毒软件一次检查数千种病毒的方式。但我不知道在哪里可以完成实现,因此与仅使用简单的慢速方法(如正则表达式)相比,它需要您做更多的工作。

在这里查看理论:http://en.wikipedia.org/wiki/Aho-Corasick

【讨论】:

    【解决方案4】:

    首先,我希望您并没有真正“标记”所写的文字。你知道,仅仅因为有人没有在坏词前加一个空格,它不会让这个词变得不那么糟糕:-) 示例,badword,

    我会说我会在这里使用 Regex :-) 我不确定 Regex 或人造解析器是否会更快,但至少 Regex 会是一个很好的起点。正如其他人所写,您首先将文本拆分为单词,然后检查HashSet<string>

    我正在添加基于ArraySegment<char> 的代码的第二个版本。我稍后再谈。

    class Program
    {
        class ArraySegmentComparer : IEqualityComparer<ArraySegment<char>>
        {
            public bool Equals(ArraySegment<char> x, ArraySegment<char> y)
            {
                if (x.Count != y.Count)
                {
                    return false;
                }
    
                int end = x.Offset + x.Count;
    
                for (int i = x.Offset, j = y.Offset; i < end; i++, j++)
                {
                    if (!x.Array[i].ToString().Equals(y.Array[j].ToString(), StringComparison.InvariantCultureIgnoreCase))
                    {
                        return false;
                    }
                }
    
                return true;
            }
    
            public override int GetHashCode(ArraySegment<char> obj)
            {
                unchecked
                {
                    int hash = 17;
    
                    int end = obj.Offset + obj.Count;
    
                    int i;
    
                    for (i = obj.Offset; i < end; i++)
                    {
                        hash *= 23;
                        hash += Char.ToUpperInvariant(obj.Array[i]);
                    }
    
                    return hash;
                }
            }
        }
    
        static void Main()
        {
            var rx = new Regex(@"\b\w+\b", RegexOptions.Compiled);
    
            var sampleText = @"For my custom made chat screen i am using the code below for checking censored words. But i wonder can this code performance improved. Thank you.
    
    if (srMessageTemp.IndexOf("" censored1 "") != -1)
    return;
    if (srMessageTemp.IndexOf("" censored2 "") != -1)
    return;
    if (srMessageTemp.IndexOf("" censored3 "") != -1)
    return;
    C# 4.0 . actually list is a lot more long but i don't put here as it goes away.
    
    And now some accented letters àèéìòù and now some letters with unicode combinable diacritics àèéìòù";
    
            //sampleText += sampleText;
            //sampleText += sampleText;
            //sampleText += sampleText;
            //sampleText += sampleText;
            //sampleText += sampleText;
            //sampleText += sampleText;
            //sampleText += sampleText;
    
            HashSet<string> prohibitedWords = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase) { "For", "custom", "combinable", "away" };
    
            Stopwatch sw1 = Stopwatch.StartNew();
    
            var words = rx.Matches(sampleText);
    
            foreach (Match word in words)
            {
                string str = word.Value;
    
                if (prohibitedWords.Contains(str))
                {
                    Console.Write(str);
                    Console.Write(" ");
                }
                else
                {
                    //Console.WriteLine(word);
                }
            }
    
            sw1.Stop();
    
            Console.WriteLine();
            Console.WriteLine();
    
            HashSet<ArraySegment<char>> prohibitedWords2 = new HashSet<ArraySegment<char>>(
                prohibitedWords.Select(p => new ArraySegment<char>(p.ToCharArray())),
                new ArraySegmentComparer());
    
            var sampleText2 = sampleText.ToCharArray();
    
            Stopwatch sw2 = Stopwatch.StartNew();
    
            int startWord = -1;
    
            for (int i = 0; i < sampleText2.Length; i++)
            {
                if (Char.IsLetter(sampleText2[i]) || Char.IsDigit(sampleText2[i]))
                {
                    if (startWord == -1)
                    {
                        startWord = i;
                    }
                }
                else
                {
                    if (startWord != -1)
                    {
                        int length = i - startWord;
    
                        if (length != 0)
                        {
                            var wordSegment = new ArraySegment<char>(sampleText2, startWord, length);
    
                            if (prohibitedWords2.Contains(wordSegment))
                            {
                                Console.Write(sampleText2, startWord, length);
                                Console.Write(" ");
                            }
                            else
                            {
                                //Console.WriteLine(sampleText2, startWord, length);
                            }
                        }
    
                        startWord = -1;
                    }
                }
            }
    
            if (startWord != -1)
            {
                int length = sampleText2.Length - startWord;
    
                if (length != 0)
                {
                    var wordSegment = new ArraySegment<char>(sampleText2, startWord, length);
    
                    if (prohibitedWords2.Contains(wordSegment))
                    {
                        Console.Write(sampleText2, startWord, length);
                        Console.Write(" ");
                    }
                    else
                    {
                        //Console.WriteLine(sampleText2, startWord, length);
                    }
                }
            }
    
            sw2.Stop();
    
            Console.WriteLine();
            Console.WriteLine();
    
            Console.WriteLine(sw1.ElapsedTicks);
            Console.WriteLine(sw2.ElapsedTicks);
        }
    }
    

    我会注意到,您可以更快地“在”原始字符串中进行解析。这意味着什么:如果您将“文档”细分为单词,并且每个单词都放在string 中,那么显然您正在创建n string,为文档的每个单词创建一个。但是如果你跳过这一步,直接对文档进行操作,只保留当前索引和当前单词的长度呢?那么它会更快!显然,您需要为HashSet&lt;&gt; 创建一个特殊的比较器。

    但是等等! C# 有类似的东西......它被称为ArraySegment。因此,您的文档将是 char[] 而不是 string,并且每个单词都是 ArraySegment&lt;char&gt;。显然这要复杂得多!您不能简单地使用Regexes,您必须“手动”构建解析器(但我认为转换\b\w+\b 表达式会很容易)。并且为HashSet&lt;char&gt; 创建一个比较器会有点复杂(提示:您将使用HashSet&lt;ArraySegment&lt;char&gt;&gt; 并且要审查的单词将是ArraySegments“指向”一个单词的char[],大小等于char[].Length,比如var word = new ArraySegment&lt;char&gt;("tobecensored".ToCharArray());)

    经过一些简单的基准测试后,我可以看到使用ArraySegment&lt;string&gt; 的程序的未优化版本与Regex 版本一样快对于较短的文本。这可能是因为如果一个词的长度为 4-6 个字符,那么复制它比复制一个 ArraySegment&lt;char&gt;ArraySegment&lt;char&gt; 是 12 个字节,一个 6 个字符的词是 12 个字节)要“慢”得多。在这两者之上,我们必须增加一点开销......但最终数字是可比的)。但是对于较长的文本(尝试取消注释 //sampleText += sampleText;),它在 Release -> Start without Debugging (CTRL-F5) 中变得更快(10%)

    我会注意到逐个字符比较字符串是错误的。您应该始终使用string 类(或操作系统)提供给您的方法。他们知道如何比你更好地处理“奇怪”的情况(在 Unicode 中没有任何“正常”的情况 :-))

    【讨论】:

    • 嗯,你对 "," 有很好的看法。是的,我在用 "" 检查之前替换了这些字符。你的解释真的很混乱。我想要的只是获得最大的性能。 gyurisc 建议使用 linq。你认为这会是最好的吗?我当然可以把句子分成单词。但是检查每个被审查的单词会更快吗?我还可以创建一个字符串哈希集。但这会更快吗?
    • @MonsterMMORPG 如果您要“替换”字符,那么如果您想要的只是速度,那么您可能已经做错了。
    • @MonsterMMORPG 更新了代码。使用 ArraySegment 与直接使用 Regex 处理小文本具有相同的速度,但编写起来要复杂得多。对于更大的文本,它的速度提高了 10%。
    • @MonsterMMORPG If you are "replacing" characters then you are probably already doing it wrong if all you want is speed. 这是因为处理一大块文本很慢。我希望您在文本前添加一个空格并在文本后添加一个空格,否则 srMessageTemp.IndexOf(" censored1 ") 将无法匹配 "censored1"
    • 感谢您的回答。但我想问这个。制作一个单词的字符串数组。给定的句子最多 140 个字符。然后在循环中检查数组是否包含任何审查会更快还是使用 linq 会更快?哪一个?
    【解决方案5】:

    您可以为此使用 linq,但如果您使用列表来保存审查值列表,则不需要这样做。下面的解决方案使用内置列表函数,并允许您进行不区分大小写的搜索。

    private static List<string> _censoredWords = new List<string>()
                                                      {
                                                          "badwordone1",
                                                          "badwordone2",
                                                          "badwordone3",
                                                          "badwordone4",
                                                      };
    
    
            static void Main(string[] args)
            {
                string badword1 = "BadWordOne2";
                bool censored = ShouldCensorWord(badword1);
            }
    
            private static bool ShouldCensorWord(string word)
            {
                return _censoredWords.Contains(word.ToLower());
            }
    

    【讨论】:

    • 这行不通。我们正在检查用户输入的句子是否包含任何坏词
    • 如果是这种情况,那么您只需使用该句子 - 使用 string.Split 方法创建一个字符串数组 - 并将每个字符串传递给 ShouldCensorWord 方法.......
    【解决方案6】:

    您对此有何看法:

    string[] censoredWords = new[] { " censored1 ", " censored2 ", " censored3 " };
    
    if (censoredWords.Contains(srMessageTemp))
       return;
    

    【讨论】:

      猜你喜欢
      • 2011-05-09
      • 1970-01-01
      • 1970-01-01
      • 2015-07-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多