diff --git a/README.MD b/README.MD index b2c505c..7bb8c95 100644 --- a/README.MD +++ b/README.MD @@ -52,4 +52,43 @@ ToolGood.Words Assert.AreEqual("all", all[1].SearchString); ``` +字符搜索优化`StringSearch` + +TextSearch : 184ms +TrieFilter : 175ms +FastFilter : 106ms +StringSearch : 32ms +----------------------- Find All ----------------------------------- +TextSearch : 1,572ms +TrieFilter : 1,360ms +FastFilter : 308ms +StringSearch : 376ms + + +注:`FastFilter`来源: http://www.cnblogs.com/yeerh/archive/2011/10/20/2219035.html + +注: 在 Find All测试中,`FastFilter`只能检测出7个: + [0]: "主席" + [1]: "赵洪祝" + [2]: "中国" + [3]: "铁道部" + [4]: "党" + [5]: "胡锦涛" + [6]: "倒台" + +`StringSearch`检测出14个: + [0]: "党" + [1]: "党委" + [2]: "西藏" + [3]: "党" + [4]: "党委" + [5]: "主席" + [6]: "赵洪祝" + [7]: "中国" + [8]: "铁道部" + [9]: "党" + [10]: "胡锦涛" + [11]: "锦涛" + [12]: "倒台" + [13]: "黑社会" diff --git a/ToolGood.Words.Contrast/Program.cs b/ToolGood.Words.Contrast/Program.cs index b2f4237..465a770 100644 --- a/ToolGood.Words.Contrast/Program.cs +++ b/ToolGood.Words.Contrast/Program.cs @@ -11,43 +11,28 @@ namespace ToolGood.Words.Contrast { class Program { - //static HashFilter hf = new HashFilter(); - static TrieSearch tf = new TrieSearch(); - //static TrieSearch2 ts1 = new TrieSearch2(); static TrieFilter tf1 = new TrieFilter(); static FastFilter ff = new FastFilter(); - + static StringSearch word = new StringSearch(); static TextSearch search; - //static StringSearch stringSearch; - //static IllegalWords words_1 = new IllegalWords(); - //static IllegalWords words_1_quick; - //static IllegalWords words_2; - //static IllegalWords words_2_quick; static void Main(string[] args) { ReadBadWord(); var text = File.ReadAllText("Talk.txt"); - //ts1.HasBadWord(text); - Run("TrieSearch", () => { tf.HasBadWord(text); }); - //Run("TrieSearch 1 ", () => { ts1.HasBadWord(text); }); Run("TextSearch ", () => { search.ContainsAny(text); }); - - Run("TrieFilter", () => { tf1.HasBadWord(text); }); Run("FastFilter", () => { ff.HasBadWord(text); }); + Run("StringSearch ", () => { word.ContainsAny(text); }); Console.WriteLine("----------------------- Find All -----------------------------------"); - Run("TrieSearch", () => { tf.FindAll(text); }); - //Run("TrieSearch 1 ", () => { ts1.FindAll(text); }); Run("TextSearch ", () => { search.FindAll(text); }); - Run("TrieFilter", () => { tf1.FindAll(text); }); - Run("FastFilter", () => { ff.FindAll(text); }); + Run("StringSearch ", () => { word.FindAll(text); }); Console.ReadKey(); @@ -67,16 +52,12 @@ namespace ToolGood.Words.Contrast static void ReadBadWord() { - //stringSearch = new StringSearch(); List list = new List(); using (StreamReader sw = new StreamReader(File.OpenRead("BadWord.txt"))) { string key = sw.ReadLine(); while (key != null) { if (key != string.Empty) { - //hf.AddKey(key); - tf.AddKey(key); tf1.AddKey(key); - //ts1.AddKey(key); ff.AddKey(key); @@ -87,7 +68,7 @@ namespace ToolGood.Words.Contrast } search = new TextSearch(); search.Keywords = list.ToArray(); - + word.SetKeywords(list); } diff --git a/ToolGood.Words/FastSearch.cs b/ToolGood.Words/FastSearch.cs deleted file mode 100644 index d3346f6..0000000 --- a/ToolGood.Words/FastSearch.cs +++ /dev/null @@ -1,196 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Collections; -using Number = System.UInt16; -using HashSet = ToolGood.Words.HashStringSet; - -namespace ToolGood.Words -{ - /// - /// 使用Hash优化算法 - /// - public class FastSearch - { - const int CharCount = char.MaxValue + 1; - private int maxWordLength = 0; - private int minWordLength = int.MaxValue; - private HashSet m_hashSet = new HashSet(); - private Number[] m_fastCheck = new Number[CharCount]; - private Number[] m_startLength = new Number[CharCount]; - private Number[] m_endLength = new Number[CharCount]; - - - public void AddKey(string word) - { - const int maxLen = sizeof(Number) * 8; - if (word.Length > maxLen) - { - throw new Exception("参数最大" + maxLen + "个字符"); - } - maxWordLength = Math.Max(maxWordLength, word.Length); - minWordLength = Math.Min(minWordLength, word.Length); - - //字符出现的位置(1-16), - for (int i = 0; i < word.Length; i++) - { - m_fastCheck[word[i]] |= (Number)(1 << i); - } - - Number mask = (Number)(1 << (word.Length - 1)); - //以x开始的字符的长度 - m_startLength[word[0]] |= mask; - //以y结束的字符的长度 - m_endLength[word[word.Length - 1]] |= mask; - m_hashSet.Add(word); - } - - public bool HasBadWord(string text) - { - for (int index = 0; index < text.Length; index++) - { - int count = 0; - int maxIndex = Math.Min(maxWordLength + index, text.Length); - char begin = text[index]; - - for (int j = index; j < maxIndex; j++) - { - char current = text[j]; - Number mask = (Number)(1 << count); - //先判断字符出现的位置是否匹配 - if ((m_fastCheck[current] & mask) == 0) - { - if (count > 1) - { - index += (count - 1); - } - break; - } - ++count; - //再判断尾字符和首字符的长度是否有匹配. - if ((m_endLength[current] & mask) != 0 && (m_startLength[begin] & mask) != 0) - { - //进行hash比较 - if (m_hashSet.Contains(text, index, count)) - { - return true; - } - } - } - } - return false; - } - - public string FindFirst(string text) - { - for (int index = 0; index < text.Length; index++) - { - int count = 0; - int maxIndex = Math.Min(maxWordLength + index, text.Length); - char begin = text[index]; - - for (int j = index; j < maxIndex; j++) - { - char current = text[j]; - Number mask = (Number)(1 << count); - //先判断字符出现的位置是否匹配 - if ((m_fastCheck[current] & mask) == 0) - { - if (count > 1) - { - index += (count - 1); - } - break; - } - ++count; - //再判断尾字符和首字符的长度是否有匹配. - if ((m_endLength[current] & mask) != 0 && (m_startLength[begin] & mask) != 0) - { - //进行hash比较 - if (m_hashSet.Contains(text, index, count)) - { - return text.Substring(index, count); - } - } - } - } - return string.Empty; - } - - public List FindAll(string text) - { - List result = new List(); - for (int index = 0; index < text.Length; index++) - { - int count = 0; - int maxIndex = Math.Min(maxWordLength + index, text.Length); - char begin = text[index]; - for (int j = index; j < maxIndex; j++) - { - char current = text[j]; - Number mask = (Number)(1 << count); - if ((m_fastCheck[current] & mask) == 0) - { - if (count > 1) - { - index += (count - 1); - } - break; - } - ++count; - if ((m_endLength[current] & mask) != 0 && (m_startLength[begin] & mask) != 0) - { - if (m_hashSet.Contains(text, index, count)) - { - result.Add(text.Substring(index, count)); - index += (count - 1); - break; - } - } - } - } - return result; - } - - public string Replace(string text, char maskChar = '*') - { - char[] chars = null; - for (int index = 0; index < text.Length; index++) - { - int count = 0; - int maxIndex = Math.Min(maxWordLength + index, text.Length); - char begin = text[index]; - - for (int j = index; j < maxIndex; j++) - { - char current = text[j]; - Number mask = (Number)(1 << count); - if ((m_fastCheck[current] & mask) == 0) - { - if (count > 1) - { - index += (count - 1); - } - break; - } - ++count; - if ((m_endLength[current] & mask) != 0 && (m_startLength[begin] & mask) != 0) - { - if (m_hashSet.Contains(text, index, count)) - { - if (chars == null) chars = text.ToArray(); - for (int i = index; i < index + count; i++) - { - chars[i] = maskChar; - } - index += (count - 1); - break; - } - } - } - } - return chars == null ? text : new string(chars); - } - } -} diff --git a/ToolGood.Words/IllegalWordDetection.cs b/ToolGood.Words/IllegalWordDetection.cs index fe671ae..a0001e0 100644 --- a/ToolGood.Words/IllegalWordDetection.cs +++ b/ToolGood.Words/IllegalWordDetection.cs @@ -1,233 +1,233 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Text; +//using System; +//using System.Collections; +//using System.Collections.Generic; +//using System.Linq; +//using System.Text; -namespace ToolGood.Words -{ - public class IllegalWordDetection - { - /// - /// 存了所有的长度大于1的敏感词汇 - /// - static HashSet wordsSet = new HashSet(); - /// - /// 存了某一个词在所有敏感词中的位置,(超出8个的截断为第8个位置) - /// - static byte[] fastCheck = new byte[char.MaxValue]; - /// - /// 存了所有敏感词的长度信息,“Key”值为所有敏感词的第一个词,敏感词的长度会截断为8 - /// - static byte[] fastLength = new byte[char.MaxValue]; - /// - /// 保有所有敏感词汇的第一个词的记录,可用来判断是否一个词是一个或者多个敏感词汇的“第一个词”,且可判断以某一个词作为第一个词的一系列的敏感词的最大的长度 - /// - static byte[] startCache = new byte[char.MaxValue]; - static char[] dectectedBuffer = null; - const string SkipList = " \t\r\n~!@#$%^&*()_+-=【】、{}|;':\",。、《》?αβγδεζηθικλμνξοπρστυφχψωΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ。,、;:?!…—·ˉ¨‘’“”々~‖∶"'`|〃〔〕〈〉《》「」『』.〖〗【】()[]{}ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ⒈⒉⒊⒋⒌⒍⒎⒏⒐⒑⒒⒓⒔⒕⒖⒗⒘⒙⒚⒛㈠㈡㈢㈣㈤㈥㈦㈧㈨㈩①②③④⑤⑥⑦⑧⑨⑩⑴⑵⑶⑷⑸⑹⑺⑻⑼⑽⑾⑿⒀⒁⒂⒃⒄⒅⒆⒇≈≡≠=≤≥<>≮≯∷±+-×÷/∫∮∝∞∧∨∑∏∪∩∈∵∴⊥∥∠⌒⊙≌∽√§№☆★○●◎◇◆□℃‰€■△▲※→←↑↓〓¤°#&@\︿_ ̄―♂♀┌┍┎┐┑┒┓─┄┈├┝┞┟┠┡┢┣│┆┊┬┭┮┯┰┱┲┳┼┽┾┿╀╁╂╃└┕┖┗┘┙┚┛━┅┉┤┥┦┧┨┩┪┫┃┇┋┴┵┶┷┸┹┺┻╋╊╉╈╇╆╅╄"; - static BitArray SkipBitArray = new BitArray(char.MaxValue); - /// - /// 保有所有敏感词汇的最后一个词的记录,仅用来判断是否一个词是一个或者多个敏感词汇的“最后一个词” - /// - static BitArray endCache = new BitArray(char.MaxValue); +//namespace ToolGood.Words +//{ +// public class IllegalWordDetection +// { +// /// +// /// 存了所有的长度大于1的敏感词汇 +// /// +// static HashSet wordsSet = new HashSet(); +// /// +// /// 存了某一个词在所有敏感词中的位置,(超出8个的截断为第8个位置) +// /// +// static byte[] fastCheck = new byte[char.MaxValue]; +// /// +// /// 存了所有敏感词的长度信息,“Key”值为所有敏感词的第一个词,敏感词的长度会截断为8 +// /// +// static byte[] fastLength = new byte[char.MaxValue]; +// /// +// /// 保有所有敏感词汇的第一个词的记录,可用来判断是否一个词是一个或者多个敏感词汇的“第一个词”,且可判断以某一个词作为第一个词的一系列的敏感词的最大的长度 +// /// +// static byte[] startCache = new byte[char.MaxValue]; +// static char[] dectectedBuffer = null; +// const string SkipList = " \t\r\n~!@#$%^&*()_+-=【】、{}|;':\",。、《》?αβγδεζηθικλμνξοπρστυφχψωΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ。,、;:?!…—·ˉ¨‘’“”々~‖∶"'`|〃〔〕〈〉《》「」『』.〖〗【】()[]{}ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ⒈⒉⒊⒋⒌⒍⒎⒏⒐⒑⒒⒓⒔⒕⒖⒗⒘⒙⒚⒛㈠㈡㈢㈣㈤㈥㈦㈧㈨㈩①②③④⑤⑥⑦⑧⑨⑩⑴⑵⑶⑷⑸⑹⑺⑻⑼⑽⑾⑿⒀⒁⒂⒃⒄⒅⒆⒇≈≡≠=≤≥<>≮≯∷±+-×÷/∫∮∝∞∧∨∑∏∪∩∈∵∴⊥∥∠⌒⊙≌∽√§№☆★○●◎◇◆□℃‰€■△▲※→←↑↓〓¤°#&@\︿_ ̄―♂♀┌┍┎┐┑┒┓─┄┈├┝┞┟┠┡┢┣│┆┊┬┭┮┯┰┱┲┳┼┽┾┿╀╁╂╃└┕┖┗┘┙┚┛━┅┉┤┥┦┧┨┩┪┫┃┇┋┴┵┶┷┸┹┺┻╋╊╉╈╇╆╅╄"; +// static BitArray SkipBitArray = new BitArray(char.MaxValue); +// /// +// /// 保有所有敏感词汇的最后一个词的记录,仅用来判断是否一个词是一个或者多个敏感词汇的“最后一个词” +// /// +// static BitArray endCache = new BitArray(char.MaxValue); - unsafe public static void Init(string[] badwords) - { - if (badwords == null || badwords.Length == 0) - return; +// unsafe public static void Init(string[] badwords) +// { +// if (badwords == null || badwords.Length == 0) +// return; - int wordLength = 0; - int maxWordLength = int.MinValue; +// int wordLength = 0; +// int maxWordLength = int.MinValue; - for (int stringIndex = 0, len = badwords.Length; stringIndex < len; ++stringIndex) { - if (string.IsNullOrEmpty(badwords[stringIndex])) - continue; +// for (int stringIndex = 0, len = badwords.Length; stringIndex < len; ++stringIndex) { +// if (string.IsNullOrEmpty(badwords[stringIndex])) +// continue; - string strBadWord = OriginalToLower(badwords[stringIndex]); - ///求得单个的敏感词汇的长度 - wordLength = strBadWord.Length; - maxWordLength = Math.Max(wordLength, maxWordLength); +// string strBadWord = OriginalToLower(badwords[stringIndex]); +// ///求得单个的敏感词汇的长度 +// wordLength = strBadWord.Length; +// maxWordLength = Math.Max(wordLength, maxWordLength); - fixed (char* pWordStart = strBadWord) { - for (int i = 0; i < wordLength; ++i) { - ///准确记录8位以内的敏感词汇的某个词在词汇中的“位置” - if (i < 7) - fastCheck[*(pWordStart + i)] |= (byte)(1 << i); - else///8位以外的敏感词汇的词直接限定在第8位 - fastCheck[*(pWordStart + i)] |= 0x80;///0x80在内存中即为1000 0000,因为一个byte顶多标示8位,故超出8位的都位或上0x80,截断成第8位 - } +// fixed (char* pWordStart = strBadWord) { +// for (int i = 0; i < wordLength; ++i) { +// ///准确记录8位以内的敏感词汇的某个词在词汇中的“位置” +// if (i < 7) +// fastCheck[*(pWordStart + i)] |= (byte)(1 << i); +// else///8位以外的敏感词汇的词直接限定在第8位 +// fastCheck[*(pWordStart + i)] |= 0x80;///0x80在内存中即为1000 0000,因为一个byte顶多标示8位,故超出8位的都位或上0x80,截断成第8位 +// } - ///缓存敏感词汇的长度 - int cachedWordslength = Math.Min(8, wordLength); - char firstWord = *pWordStart; - ///记录敏感词汇的“大致长度(超出8个字的敏感词汇会被截取成8的长度)”,“key”值为敏感词汇的第一个词 - fastLength[firstWord] |= (byte)(1 << (cachedWordslength - 1)); - ///缓存出当前以badWord第一个字开头的一系列的敏感词汇的最长的长度 - if (startCache[firstWord] < cachedWordslength) - startCache[firstWord] = (byte)(cachedWordslength); +// ///缓存敏感词汇的长度 +// int cachedWordslength = Math.Min(8, wordLength); +// char firstWord = *pWordStart; +// ///记录敏感词汇的“大致长度(超出8个字的敏感词汇会被截取成8的长度)”,“key”值为敏感词汇的第一个词 +// fastLength[firstWord] |= (byte)(1 << (cachedWordslength - 1)); +// ///缓存出当前以badWord第一个字开头的一系列的敏感词汇的最长的长度 +// if (startCache[firstWord] < cachedWordslength) +// startCache[firstWord] = (byte)(cachedWordslength); - ///存好敏感词汇的最后一个词汇的“出现情况” - endCache[*(pWordStart + wordLength - 1)] = true; - ///将长度大于1的敏感词汇都压入到字典中 - if (!wordsSet.Contains(strBadWord)) - wordsSet.Add(strBadWord); - } - } +// ///存好敏感词汇的最后一个词汇的“出现情况” +// endCache[*(pWordStart + wordLength - 1)] = true; +// ///将长度大于1的敏感词汇都压入到字典中 +// if (!wordsSet.Contains(strBadWord)) +// wordsSet.Add(strBadWord); +// } +// } - /// 初始化好一个用来存检测到的字符串的buffer - dectectedBuffer = new char[maxWordLength]; - /// 记录应该跳过的不予检测的词 - fixed (char* start = SkipList) { - char* itor = start; - char* end = start + SkipList.Length; - while (itor < end) SkipBitArray[*itor++] = true; - } - } +// /// 初始化好一个用来存检测到的字符串的buffer +// dectectedBuffer = new char[maxWordLength]; +// /// 记录应该跳过的不予检测的词 +// fixed (char* start = SkipList) { +// char* itor = start; +// char* end = start + SkipList.Length; +// while (itor < end) SkipBitArray[*itor++] = true; +// } +// } - unsafe static string OriginalToLower(string text) - { - fixed (char* newText = text) { - char* itor = newText; - char* end = newText + text.Length; - char c; +// unsafe static string OriginalToLower(string text) +// { +// fixed (char* newText = text) { +// char* itor = newText; +// char* end = newText + text.Length; +// char c; - while (itor < end) { - c = *itor; +// while (itor < end) { +// c = *itor; - if ('A' <= c && c <= 'Z') { - *itor = (char)(c | 0x20); - } +// if ('A' <= c && c <= 'Z') { +// *itor = (char)(c | 0x20); +// } - ++itor; - } - } +// ++itor; +// } +// } - return text; - } +// return text; +// } - unsafe static bool EnsuranceLower(string text) - { - fixed (char* newText = text) { - char* itor = newText; - char* end = newText + text.Length; - char c; +// unsafe static bool EnsuranceLower(string text) +// { +// fixed (char* newText = text) { +// char* itor = newText; +// char* end = newText + text.Length; +// char c; - while (itor < end) { - c = *itor; +// while (itor < end) { +// c = *itor; - if ('A' <= c && c <= 'Z') { - return true; - } +// if ('A' <= c && c <= 'Z') { +// return true; +// } - ++itor; - } - } +// ++itor; +// } +// } - return false; - } +// return false; +// } - /// - /// 过滤字符串,默认遇到敏感词汇就以'*'代替 - /// - /// - /// - /// - unsafe public static string Filter(string text, string mask = "*") - { - Dictionary dic = DetectIllegalWords(text); - ///如果没有敏感词汇,则直接返回出去 - if (dic.Count == 0) - return text; +// /// +// /// 过滤字符串,默认遇到敏感词汇就以'*'代替 +// /// +// /// +// /// +// /// +// unsafe public static string Filter(string text, string mask = "*") +// { +// Dictionary dic = DetectIllegalWords(text); +// ///如果没有敏感词汇,则直接返回出去 +// if (dic.Count == 0) +// return text; - fixed (char* newText = text, cMask = mask) { - var itor = newText; - Dictionary.Enumerator enumerator = dic.GetEnumerator(); - ///开始替换敏感词汇 - while (enumerator.MoveNext()) { - ///偏移到敏感词出现的位置 - itor = newText + enumerator.Current.Key; - for (int index = 0; index < enumerator.Current.Value; index++) { - ///屏蔽掉敏感词汇 - *itor++ = *cMask; - } - } - enumerator.Dispose(); - } +// fixed (char* newText = text, cMask = mask) { +// var itor = newText; +// Dictionary.Enumerator enumerator = dic.GetEnumerator(); +// ///开始替换敏感词汇 +// while (enumerator.MoveNext()) { +// ///偏移到敏感词出现的位置 +// itor = newText + enumerator.Current.Key; +// for (int index = 0; index < enumerator.Current.Value; index++) { +// ///屏蔽掉敏感词汇 +// *itor++ = *cMask; +// } +// } +// enumerator.Dispose(); +// } - return text; - } +// return text; +// } - /// - /// 判断text是否有敏感词汇,如果有返回敏感的词汇的位置,利用指针操作来加快运算速度 - /// - /// - /// - unsafe public static Dictionary DetectIllegalWords(string text) - { - var findResult = new Dictionary(); - if (string.IsNullOrEmpty(text)) - return findResult; - if (EnsuranceLower(text)) - text = text.ToLower(); +// /// +// /// 判断text是否有敏感词汇,如果有返回敏感的词汇的位置,利用指针操作来加快运算速度 +// /// +// /// +// /// +// unsafe public static Dictionary DetectIllegalWords(string text) +// { +// var findResult = new Dictionary(); +// if (string.IsNullOrEmpty(text)) +// return findResult; +// if (EnsuranceLower(text)) +// text = text.ToLower(); - fixed (char* ptext = text, detectedStrStart = dectectedBuffer) { - ///缓存字符串的初始位置 - char* itor = (fastCheck[*ptext] & 0x01) == 0 ? ptext + 1 : ptext; - ///缓存字符串的末尾位置 - char* end = ptext + text.Length; +// fixed (char* ptext = text, detectedStrStart = dectectedBuffer) { +// ///缓存字符串的初始位置 +// char* itor = (fastCheck[*ptext] & 0x01) == 0 ? ptext + 1 : ptext; +// ///缓存字符串的末尾位置 +// char* end = ptext + text.Length; - while (itor < end) { - ///如果text的第一个词不是敏感词汇或者当前遍历到了text第一个词的后面的词,则循环检测到text词汇的倒数第二个词,看看这一段子字符串中有没有敏感词汇 - if ((fastCheck[*itor] & 0x01) == 0) { - while (itor < end - 1 && (fastCheck[*(++itor)] & 0x01) == 0) ; - } +// while (itor < end) { +// ///如果text的第一个词不是敏感词汇或者当前遍历到了text第一个词的后面的词,则循环检测到text词汇的倒数第二个词,看看这一段子字符串中有没有敏感词汇 +// if ((fastCheck[*itor] & 0x01) == 0) { +// while (itor < end - 1 && (fastCheck[*(++itor)] & 0x01) == 0) ; +// } - ///如果有只有一个词的敏感词,且当前的字符串的“非第一个词”满足这个敏感词,则先加入已检测到的敏感词列表 - if (startCache[*itor] != 0 && (fastLength[*itor] & 0x01) > 0) { - ///返回敏感词在text中的位置,以及敏感词的长度,供过滤功能用 - findResult.Add((int)(itor - ptext), 1); - } +// ///如果有只有一个词的敏感词,且当前的字符串的“非第一个词”满足这个敏感词,则先加入已检测到的敏感词列表 +// if (startCache[*itor] != 0 && (fastLength[*itor] & 0x01) > 0) { +// ///返回敏感词在text中的位置,以及敏感词的长度,供过滤功能用 +// findResult.Add((int)(itor - ptext), 1); +// } - char* strItor = detectedStrStart; - *strItor++ = *itor; - int remainLength = (int)(end - itor - 1); - int skipCount = 0; - ///此时已经检测到一个敏感词的“首词”了,记录下第一个检测到的敏感词的位置 - ///从当前的位置检测到字符串末尾 - for (int i = 1; i <= remainLength; ++i) { - char* subItor = itor + i; - /// 跳过一些过滤的字符,比如空格特殊符号之类的 - if (SkipBitArray[*subItor]) { - ++skipCount; - continue; - } - ///如果检测到当前的词在所有敏感词中的位置信息中没有处在第i位的,则马上跳出遍历 - if ((fastCheck[*subItor] >> Math.Min(i - skipCount, 7)) == 0) { - break; - } +// char* strItor = detectedStrStart; +// *strItor++ = *itor; +// int remainLength = (int)(end - itor - 1); +// int skipCount = 0; +// ///此时已经检测到一个敏感词的“首词”了,记录下第一个检测到的敏感词的位置 +// ///从当前的位置检测到字符串末尾 +// for (int i = 1; i <= remainLength; ++i) { +// char* subItor = itor + i; +// /// 跳过一些过滤的字符,比如空格特殊符号之类的 +// if (SkipBitArray[*subItor]) { +// ++skipCount; +// continue; +// } +// ///如果检测到当前的词在所有敏感词中的位置信息中没有处在第i位的,则马上跳出遍历 +// if ((fastCheck[*subItor] >> Math.Min(i - skipCount, 7)) == 0) { +// break; +// } - *strItor++ = *subItor; - ///如果有检测到敏感词的最后一个词,并且此时的“检测到的敏感词汇”的长度也符合要求,则才进一步查看检测到的敏感词汇是否是真的敏感 - if ((fastLength[*itor] >> Math.Min(i - 1 - skipCount, 7)) > 0 && endCache[*subItor]) { - ///如果此子字符串在敏感词字典中存在,则记录。做此判断是避免敏感词中夹杂了其他敏感词的单词,而上面的算法无法剔除,故先用hash数组来剔除 - ///上述算法是用于减少大部分的比较消耗 - if (wordsSet.Contains(new string(dectectedBuffer, 0, (int)(strItor - detectedStrStart)))) { - int curDectectedStartIndex = (int)(itor - ptext); - findResult[curDectectedStartIndex] = i + 1; - itor = subItor; - break; - } - } else if (i - skipCount > startCache[*itor] && startCache[*itor] < 0x80)///如果超过了以该词为首的一系列的敏感词汇的最大的长度,则不继续判断(前提是该词对应的所有敏感词汇没有超过8个词的) - { - break; - } - } - ++itor; - } - } +// *strItor++ = *subItor; +// ///如果有检测到敏感词的最后一个词,并且此时的“检测到的敏感词汇”的长度也符合要求,则才进一步查看检测到的敏感词汇是否是真的敏感 +// if ((fastLength[*itor] >> Math.Min(i - 1 - skipCount, 7)) > 0 && endCache[*subItor]) { +// ///如果此子字符串在敏感词字典中存在,则记录。做此判断是避免敏感词中夹杂了其他敏感词的单词,而上面的算法无法剔除,故先用hash数组来剔除 +// ///上述算法是用于减少大部分的比较消耗 +// if (wordsSet.Contains(new string(dectectedBuffer, 0, (int)(strItor - detectedStrStart)))) { +// int curDectectedStartIndex = (int)(itor - ptext); +// findResult[curDectectedStartIndex] = i + 1; +// itor = subItor; +// break; +// } +// } else if (i - skipCount > startCache[*itor] && startCache[*itor] < 0x80)///如果超过了以该词为首的一系列的敏感词汇的最大的长度,则不继续判断(前提是该词对应的所有敏感词汇没有超过8个词的) +// { +// break; +// } +// } +// ++itor; +// } +// } - return findResult; - } - } -} +// return findResult; +// } +// } +//} diff --git a/ToolGood.Words/Properties/AssemblyInfo.cs b/ToolGood.Words/Properties/AssemblyInfo.cs index c0ee689..02de789 100644 --- a/ToolGood.Words/Properties/AssemblyInfo.cs +++ b/ToolGood.Words/Properties/AssemblyInfo.cs @@ -32,5 +32,5 @@ using System.Runtime.InteropServices; //可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值, // 方法是按如下所示使用“*”: : // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.1.2.0")] -[assembly: AssemblyFileVersion("1.1.2.0")] +[assembly: AssemblyVersion("1.2.0.0")] +[assembly: AssemblyFileVersion("1.2.0.0")] diff --git a/ToolGood.Words/StringSearch.cs b/ToolGood.Words/StringSearch.cs new file mode 100644 index 0000000..b7fc28f --- /dev/null +++ b/ToolGood.Words/StringSearch.cs @@ -0,0 +1,344 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace ToolGood.Words +{ + public class StringSearch + { + class TreeNode + { + #region Constructor & Methods + + public TreeNode(TreeNode parent, char c) + { + _char = c; _parent = parent; + _results = new List(); + + _transitionsAr = new List(); + _transHash = new Dictionary(); + } + + public void AddResult(string result) + { + if (_results.Contains(result)) return; + _results.Add(result); + } + + public void AddTransition(TreeNode node) + { + _transHash.Add(node.Char, node); + _transitionsAr.Add(node); + } + + public TreeNode GetTransition(char c) + { + TreeNode tn; + if (_transHash.TryGetValue(c, out tn)) { return tn; } + return null; + } + + public bool ContainsTransition(char c) + { + return _transHash.ContainsKey(c); + } + #endregion + + #region Properties + private char _char; + private TreeNode _parent; + private TreeNode _failure; + private List _results; + private List _transitionsAr; + private Dictionary _transHash; + + public char Char + { + get { return _char; } + } + + public TreeNode Parent + { + get { return _parent; } + } + + + /// + /// Failure function - descendant node + /// + public TreeNode Failure + { + get { return _failure; } + set { _failure = value; } + } + + + /// + /// Transition function - list of descendant nodes + /// + public List Transitions + { + get { return _transitionsAr; } + } + + + /// + /// Returns list of patterns ending by this letter + /// + public List Results + { + get { return _results; } + } + + #endregion + } + class TrieNode + { + public bool End { get; set; } + public HashSet Results { get; set; } + private Dictionary m_values; + private uint minflag = uint.MaxValue; + private uint maxflag = uint.MinValue; + + public TrieNode() + { + m_values = new Dictionary(); + Results = new HashSet(); + } + + public bool TryGetValue(char c, out TrieNode node) + { + if (minflag <= (uint)c && maxflag >= (uint)c) { + return m_values.TryGetValue(c, out node); + } + node = null; + return false; + } + + public void Add(TreeNode t, TrieNode node) + { + var c = t.Char; + if (m_values.ContainsKey(c) == false) { + if (minflag > c) { minflag = c; } + if (maxflag < c) { maxflag = c; } + m_values.Add(c, node); + foreach (var item in t.Results) { + node.End = true; + node.Results.Add(item); + } + } + } + + public TrieNode[] ToArray() + { + TrieNode[] first = new TrieNode[char.MaxValue + 1]; + foreach (var item in m_values) { + first[item.Key] = item.Value; + } + return first; + } + } + + private TrieNode _root = new TrieNode(); + private TrieNode[] _first = new TrieNode[char.MaxValue + 1]; + + #region SetKeywords + public void SetKeywords(ICollection _keywords) + { + var tn = BuildTreeWithBFS(_keywords); + SimplifyTree(tn); + } + TreeNode BuildTreeWithBFS(ICollection _keywords) + { + var root = new TreeNode(null, ' '); + foreach (string p in _keywords) { + string t = p; + //if (_quick) { + // t = p.ToLower(); + //} else { + // t = WordHelper.ToSenseWord(p); + //} + + // add pattern to tree + TreeNode nd = root; + foreach (char c in t) { + TreeNode ndNew = null; + foreach (TreeNode trans in nd.Transitions) + if (trans.Char == c) { ndNew = trans; break; } + + if (ndNew == null) { + ndNew = new TreeNode(nd, c); + nd.AddTransition(ndNew); + } + nd = ndNew; + } + nd.AddResult(t); + } + + List nodes = new List(); + // Find failure functions + //ArrayList nodes = new ArrayList(); + // level 1 nodes - fail to root node + foreach (TreeNode nd in root.Transitions) { + nd.Failure = root; + foreach (TreeNode trans in nd.Transitions) nodes.Add(trans); + } + // other nodes - using BFS + while (nodes.Count != 0) { + List newNodes = new List(); + + //ArrayList newNodes = new ArrayList(); + foreach (TreeNode nd in nodes) { + TreeNode r = nd.Parent.Failure; + char c = nd.Char; + + while (r != null && !r.ContainsTransition(c)) r = r.Failure; + if (r == null) + nd.Failure = root; + else { + nd.Failure = r.GetTransition(c); + foreach (string result in nd.Failure.Results) + nd.AddResult(result); + } + + // add child nodes to BFS list + foreach (TreeNode child in nd.Transitions) + newNodes.Add(child); + } + nodes = newNodes; + } + root.Failure = root; + return root; + } + void SimplifyTree(TreeNode tn) + { + _root = new TrieNode(); + Dictionary dict = new Dictionary(); + + List list = new List(); + foreach (var item in tn.Transitions) list.Add(item); + + while (list.Count > 0) { + foreach (var item in list) { + simplifyNode(item, tn, dict); + } + List newNodes = new List(); + foreach (var item in list) { + foreach (var node in item.Transitions) { + newNodes.Add(node); + } + } + list = newNodes; + } + addNode(tn, _root, dict); + _first = _root.ToArray(); + } + void addNode(TreeNode treeNode, TrieNode tridNode, Dictionary dict) + { + foreach (var item in treeNode.Transitions) { + var node = dict[item]; + tridNode.Add(item, node); + addNode(item, node, dict); + } + } + void simplifyNode(TreeNode treeNode, TreeNode root, Dictionary dict) + { + List list = new List(); + var tn = treeNode; + while (tn != root) { + list.Add(tn); + tn = tn.Failure; + } + + TrieNode node = new TrieNode(); + + foreach (var item in list) { + if (dict.ContainsKey(item) == false) { + if (item.Results.Count > 0) { + dict[item] = new TrieNode(); + } else { + dict[item] = node; + } + } + } + } + + + + #endregion + + public string FindFirst(string text) + { + TrieNode ptr = null; + for (int i = 0; i < text.Length; i++) { + TrieNode tn; + if (ptr==null) { + tn = _first[text[i]]; + } else { + if (ptr.TryGetValue(text[i], out tn)==false) { + tn = _first[text[i]]; + } + } + if (tn!=null) { + if (tn.End) { + foreach (var item in tn.Results) { + return item; + } + } + } + ptr = tn; + } + return null; + } + + public List FindAll(string text) + { + TrieNode ptr = null; + List list = new List(); + + for (int i = 0; i < text.Length; i++) { + TrieNode tn; + if (ptr == null) { + tn = _first[text[i]]; + } else { + if (ptr.TryGetValue(text[i], out tn) == false) { + tn = _first[text[i]]; + } + } + if (tn != null) { + if (tn.End) { + foreach (var item in tn.Results) { + list.Add(item); + } + } + } + ptr = tn; + } + return list; + } + + public bool ContainsAny(string text) + { + TrieNode ptr = null; + for (int i = 0; i < text.Length; i++) { + TrieNode tn; + if (ptr == null) { + tn = _first[text[i]]; + } else { + if (ptr.TryGetValue(text[i], out tn) == false) { + tn = _first[text[i]]; + } + } + if (tn != null) { + if (tn.End) { + return true; + } + } + ptr = tn; + } + return false; + } + + } +} diff --git a/ToolGood.Words/TextSearch.cs b/ToolGood.Words/TextSearch.cs index 0b3b286..fb5d98d 100644 --- a/ToolGood.Words/TextSearch.cs +++ b/ToolGood.Words/TextSearch.cs @@ -33,6 +33,11 @@ namespace ToolGood.Words public string Keyword { get; private set; } public static TextSearchResult Empty { get { return new TextSearchResult(); } } + public override string ToString() + { + return Keyword; + } + } /// /// 完整字符搜索 diff --git a/ToolGood.Words/ToolGood.Words.csproj b/ToolGood.Words/ToolGood.Words.csproj index 8295360..8364be8 100644 --- a/ToolGood.Words/ToolGood.Words.csproj +++ b/ToolGood.Words/ToolGood.Words.csproj @@ -20,7 +20,7 @@ DEBUG;TRACE prompt 4 - true + false pdbonly @@ -29,6 +29,7 @@ TRACE prompt 4 + false true @@ -44,19 +45,16 @@ - + - - - diff --git a/ToolGood.Words/TrieSearch.cs b/ToolGood.Words/TrieSearch.cs deleted file mode 100644 index b7a0190..0000000 --- a/ToolGood.Words/TrieSearch.cs +++ /dev/null @@ -1,163 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace ToolGood.Words -{ - public class TrieSearch - { - class TrieNode - { - public bool m_end; - public Dictionary m_values; - private uint minflag = uint.MaxValue; - private uint maxflag = uint.MinValue; - - public TrieNode() - { - m_values = new Dictionary(); - } - - public virtual bool TryGetValue(char c, out TrieNode node) - { - if (minflag <= (uint)c && maxflag >= (uint)c) { - return m_values.TryGetValue(c, out node); - } - node = null; - return false; - } - - public TrieNode Add(char c) - { - if (minflag > c) { minflag = c; } - if (maxflag < c) { maxflag = c; } - - TrieNode subnode; - if (!m_values.TryGetValue(c, out subnode)) { - subnode = new TrieNode(); - m_values.Add(c, subnode); - } - return subnode; - } - - } - - TrieNode _root = new TrieNode(); - - /// - /// 添加关键字 - /// - /// - public void AddKey(string key) - { - if (string.IsNullOrEmpty(key)) { - return; - } - TrieNode node = _root; - for (int i = 0; i < key.Length; i++) { - char c = key[i]; - node = node.Add(c); - } - node.m_end = true; - } - - /// - /// 检查是否包含非法字符 - /// - /// 输入文本 - /// 找到的第1个非法字符.没有则返回string.Empty - public bool HasBadWord(string text) - { - - for (int head = 0; head < text.Length; head++) { - int index = head; - TrieNode node = _root; - while (node.TryGetValue(text[index], out node)) { - if (node.m_end) { - return true; - } - if (text.Length == ++index) { - break; - } - } - } - return false; - } - - /// - /// 检查是否包含非法字符 - /// - /// 输入文本 - /// 找到的第1个非法字符.没有则返回string.Empty - public string FindFirst(string text) - { - - for (int head = 0; head < text.Length; head++) { - int index = head; - TrieNode node = _root; - while (node.TryGetValue(text[index], out node)) { - if (node.m_end) { - return text.Substring(head, index - head + 1); - } - if (text.Length == ++index) { - break; - } - } - } - return string.Empty; - } - - /// - /// 查找所有非法字符 - /// - /// - /// - public List FindAll(string text) - { - - List result = new List(); - for (int head = 0; head < text.Length; head++) { - int index = head; - TrieNode node = _root; - while (node.TryGetValue(text[index], out node)) { - if (node.m_end) { - result.Add(text.Substring(head, index - head + 1)); - } - if (text.Length == ++index) { - break; - } - } - } - return result; - } - - /// - /// 替换非法字符 - /// - /// - /// 用于代替非法字符 - /// 替换后的字符串 - public string Replace(string text, char mask = '*') - { - char[] chars = null; - for (int head = 0; head < text.Length; head++) { - int index = head; - TrieNode node = _root; - while (node.TryGetValue(text[index], out node)) { - if (node.m_end) { - if (chars == null) chars = text.ToArray(); - for (int i = head; i <= index; i++) { - chars[i] = mask; - } - head = index; - } - if (text.Length == ++index) { - break; - } - } - } - return chars == null ? text : new string(chars); - } - } -} diff --git a/ToolGood.Words/internal/HashHelpers.cs b/ToolGood.Words/internal/HashHelpers.cs deleted file mode 100644 index 8687958..0000000 --- a/ToolGood.Words/internal/HashHelpers.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace ToolGood.Words -{ - /// - /// 从.net 编译出来的. - /// - internal static class HashHelpers - { - // Fields - internal static readonly int[] primes = new int[] { - 3, 7, 11, 0x11, 0x17, 0x1d, 0x25, 0x2f, 0x3b, 0x47, 0x59, 0x6b, 0x83, 0xa3, 0xc5, 0xef, - 0x125, 0x161, 0x1af, 0x209, 0x277, 0x2f9, 0x397, 0x44f, 0x52f, 0x63d, 0x78b, 0x91d, 0xaf1, 0xd2b, 0xfd1, 0x12fd, - 0x16cf, 0x1b65, 0x20e3, 0x2777, 0x2f6f, 0x38ff, 0x446f, 0x521f, 0x628d, 0x7655, 0x8e01, 0xaa6b, 0xcc89, 0xf583, 0x126a7, 0x1619b, - 0x1a857, 0x1fd3b, 0x26315, 0x2dd67, 0x3701b, 0x42023, 0x4f361, 0x5f0ed, 0x72125, 0x88e31, 0xa443b, 0xc51eb, 0xec8c1, 0x11bdbf, 0x154a3f, 0x198c4f, - 0x1ea867, 0x24ca19, 0x2c25c1, 0x34fa1b, 0x3f928f, 0x4c4987, 0x5b8b6f, 0x6dda89 - }; - - - internal static int GetPrime(int min) - { - for (int i = 0; i < primes.Length; i++) { - int num2 = primes[i]; - if (num2 >= min) { - return num2; - } - } - for (int j = min | 1; j < 0x7fffffff; j += 2) { - if (IsPrime(j)) { - return j; - } - } - return min; - } - - internal static bool IsPrime(int candidate) - { - if ((candidate & 1) == 0) { - return (candidate == 2); - } - int num = (int)Math.Sqrt((double)candidate); - for (int i = 3; i <= num; i += 2) { - if ((candidate % i) == 0) { - return false; - } - } - return true; - } - } -} diff --git a/ToolGood.Words/internal/HashStringSet.cs b/ToolGood.Words/internal/HashStringSet.cs deleted file mode 100644 index 738cc17..0000000 --- a/ToolGood.Words/internal/HashStringSet.cs +++ /dev/null @@ -1,227 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.InteropServices; -using System.Text; - -namespace ToolGood.Words -{ - /// - /// 参考了MONO的HashSet,针对字符串进行优化 - /// 增加了检查部分字符串的方法,可避免字符分割带来的GC问题 - /// Contains(String item, int offset, int len) - /// - class HashStringSet - { - [StructLayout(LayoutKind.Sequential)] - internal struct Slot - { - internal int hashCode; - internal String value; - internal int next; - } - - // Fields - private int[] m_buckets; - private int m_count; - private int m_freeList; - private int m_lastIndex; - private Slot[] m_slots; - - - public HashStringSet() - { - this.m_lastIndex = 0; - this.m_count = 0; - this.m_freeList = -1; - } - - public bool Add(String value) - { - int freeList; - if (this.m_buckets == null) { - this.Initialize(0); - } - int hashCode = this.InternalGetHashCode(value); - int index = hashCode % this.m_buckets.Length; - for (int i = this.m_buckets[hashCode % this.m_buckets.Length] - 1; i >= 0; i = this.m_slots[i].next) { - if ((this.m_slots[i].hashCode == hashCode) && this.m_slots[i].value.Equals(value)) { - return false; - } - } - if (this.m_freeList >= 0) { - freeList = this.m_freeList; - this.m_freeList = this.m_slots[freeList].next; - } else { - if (this.m_lastIndex == this.m_slots.Length) { - this.IncreaseCapacity(); - index = hashCode % this.m_buckets.Length; - } - freeList = this.m_lastIndex; - this.m_lastIndex++; - } - this.m_slots[freeList].hashCode = hashCode; - this.m_slots[freeList].value = value; - this.m_slots[freeList].next = this.m_buckets[index] - 1; - this.m_buckets[index] = freeList + 1; - this.m_count++; - return true; - } - - private void IncreaseCapacity() - { - int min = this.m_count * 2; - if (min < 0) { - min = this.m_count; - } - int prime = HashHelpers.GetPrime(min); - Slot[] destinationArray = new Slot[prime]; - if (this.m_slots != null) { - Array.Copy(this.m_slots, 0, destinationArray, 0, this.m_lastIndex); - } - int[] numArray = new int[prime]; - for (int i = 0; i < this.m_lastIndex; i++) { - int index = destinationArray[i].hashCode % prime; - destinationArray[i].next = numArray[index] - 1; - numArray[index] = i + 1; - } - this.m_slots = destinationArray; - this.m_buckets = numArray; - } - - public void Clear() - { - if (this.m_lastIndex > 0) { - Array.Clear(this.m_slots, 0, this.m_lastIndex); - Array.Clear(this.m_buckets, 0, this.m_buckets.Length); - this.m_lastIndex = 0; - this.m_count = 0; - this.m_freeList = -1; - } - } - - public bool Contains(String item) - { - if (this.m_buckets != null) { - int hashCode = this.InternalGetHashCode(item); - for (int i = this.m_buckets[hashCode % this.m_buckets.Length] - 1; i >= 0; i = this.m_slots[i].next) { - if ((this.m_slots[i].hashCode == hashCode) && this.m_slots[i].value.Equals(item)) { - return true; - } - } - } - return false; - } - - private void Initialize(int capacity) - { - int prime = HashHelpers.GetPrime(capacity); - this.m_buckets = new int[prime]; - this.m_slots = new Slot[prime]; - } - - //重写原来的HashCode - private int InternalGetHashCode(String item) - { - return InternalGetHashCode(item, 0, item.Length); - } - - public bool Remove(String item) - { - if (this.m_buckets != null) { - int hashCode = this.InternalGetHashCode(item); - int index = hashCode % this.m_buckets.Length; - int num3 = -1; - for (int i = this.m_buckets[index] - 1; i >= 0; i = this.m_slots[i].next) { - if ((this.m_slots[i].hashCode == hashCode) && this.m_slots[i].value.Equals(item)) { - if (num3 < 0) { - this.m_buckets[index] = this.m_slots[i].next + 1; - } else { - this.m_slots[num3].next = this.m_slots[i].next; - } - this.m_slots[i].hashCode = -1; - this.m_slots[i].value = null; - this.m_slots[i].next = this.m_freeList; - this.m_count--; - if (this.m_count == 0) { - this.m_lastIndex = 0; - this.m_freeList = -1; - } else { - this.m_freeList = i; - } - return true; - } - num3 = i; - } - } - return false; - } - - #region 新增方法,避免字符分割 - public bool Contains(String item, int offset, int len) - { - if (this.m_buckets != null) { - int hashCode = InternalGetHashCode(item, offset, len); - for (int i = this.m_buckets[hashCode % this.m_buckets.Length] - 1; i >= 0; i = this.m_slots[i].next) { - if ((this.m_slots[i].hashCode == hashCode) && StringEquals(this.m_slots[i].value, item, offset, len)) { - return true; - } - } - } - return false; - } - - static unsafe int InternalGetHashCode(String item, int offset, int len) - { - int h = 0; - fixed (char* c = item) { - char* cc = c + offset; - char* end = cc + len - 1; - for (; cc < end; cc += 2) { - h = (h << 5) - h + *cc; - h = (h << 5) - h + cc[1]; - } - ++end; - if (cc < end) - h = (h << 5) - h + *cc; - } - return h & 0x7fffffff; - } - - static unsafe bool StringEquals(string a, string b, int offset, int len) - { - if (len != a.Length) - return false; - - fixed (char* s1 = a, s2 = b) { - char* s1_ptr = s1; - char* s2_ptr = s2 + offset; - - if ((offset & 0x01) == 0) //偏移位置为偶数 - { - while (len > 1) { - if (((int*)s1_ptr)[0] != ((int*)s2_ptr)[0]) - return false; - - s1_ptr += 2; - s2_ptr += 2; - len -= 2; - } - } else //偏移位置为奇数 - { - while (len > 1) { - if ((s1_ptr)[0] != (s2_ptr)[0] || - (s1_ptr)[1] != (s2_ptr)[1]) - return false; - - s1_ptr += 2; - s2_ptr += 2; - len -= 2; - } - } - return len == 0 || *s1_ptr == *s2_ptr; - } - } - #endregion - } -}