单词审查的最佳方法 - C#4.0

时间:2011-10-23 16:38:36

标签: c# keyword

对于我的自定义聊天屏幕,我使用下面的代码来检查删失的字词。但我想知道这个代码性能是否会提高。谢谢。

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

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

6 个答案:

答案 0 :(得分:4)

答案 1 :(得分:2)

您可以简化它。这里listOfCencoredWords将包含所有被删除的单词

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

答案 2 :(得分:2)

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

请参阅此处的理论:http://en.wikipedia.org/wiki/Aho-Corasick

答案 3 :(得分:1)

首先,我希望你没有真正“书写”所写的字样。你知道,只是因为某人没有在一个坏词之前放置一个空格,它不会使这个词变坏:-)示例,badword,

我会说我会在这里使用正则表达式:-)我不确定正则表达式或人工解析器是否会更快,但至少一个正则表达式将是一个很好的起点。正如其他人写的那样,您首先将文本拆分为单词,然后检查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<>创建一个特殊的比较器。

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

在一些简单的基准测试之后,我可以看到使用ArraySegment<string>的未经优化的程序版本与用于较短文本的Regex版本一样快。这可能是因为如果一个单词的长度为4-6个字符,则复制它的速度与复制ArraySegment<char>的速度一样慢(ArraySegment<char>是12个字节,一个6个字符的单词是12个字节。除了这两个之外,我们还要增加一点开销...但最后数字是可比的)。但是对于较长的文本(尝试退出//sampleText += sampleText;),它在发布中变得快一点(10%) - &gt;无需调试即启动(CTRL-F5)

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

答案 4 :(得分:0)

您可以使用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());
        }

答案 5 :(得分:-1)

您对此有何看法:

string[] censoredWords = new[] { " censored1 ", " censored2 ", " censored3 " };

if (censoredWords.Contains(srMessageTemp))
   return;