【问题标题】:C# string comparison ignoring spaces, carriage return or line breaksC# 字符串比较忽略空格、回车或换行符
【发布时间】:2011-01-17 23:20:54
【问题描述】:

如何在 C# 中比较两个字符串,忽略大小写、空格和任何换行符。我还需要检查两个字符串是否为空,然后将它们标记为相同。

谢谢!

【问题讨论】:

标签: c# .net string


【解决方案1】:

您应该通过删除不想比较的字符来规范化每个字符串,然后您可以执行 String.Equals 和忽略大小写的 StringComparison。

类似这样的:

string s1 = "HeLLo    wOrld!";
string s2 = "Hello\n    WORLd!";

string normalized1 = Regex.Replace(s1, @"\s", "");
string normalized2 = Regex.Replace(s2, @"\s", "");

bool stringEquals = String.Equals(
    normalized1, 
    normalized2, 
    StringComparison.OrdinalIgnoreCase);

Console.WriteLine(stringEquals);

这里首先使用Regex.Replace 删除所有空白字符。此处不处理两个字符串均为 null 的特殊情况,但您可以在执行字符串规范化之前轻松处理这种情况。

【讨论】:

  • 在此处的两个字符串上使用Regex.Replace 会不会对性能产生影响?
  • 这不是很容易回答的问题。当然在性能方面有更好的解决方案,例如不需要创建两个新字符串的解决方案,但除非您证明正则表达式是您特定场景中的瓶颈,否则我不会打扰它。
  • 为什么半归一化,还需要字符串比较?
【解决方案2】:

这也可能有效。

String.Compare(s1, s2, CultureInfo.CurrentCulture, CompareOptions.IgnoreCase | CompareOptions.IgnoreSymbols) == 0

编辑:

IgnoreSymbols:表示字符串比较必须忽略符号,如 空白字符、标点符号、货币符号、百分比 符号、数学符号、& 符号等。

【讨论】:

  • 这是迄今为止最好的答案。但是它合并了非空格,这可能会导致意外的等价,例如“1 2 3”将等于“123”。
【解决方案3】:

删除所有不需要的字符,然后使用 ToLower() 方法忽略大小写。

编辑:虽然上述方法有效,但最好使用StringComparison.OrdinalIgnoreCase。只需将它作为第二个参数传递给 Equals 方法即可。

【讨论】:

  • -1:应该使用 StringComparison.OrdinalIgnoreCase,而不是 ToLower()。
  • 不会使用 OrdinalIgnoreCase 忽略当前的文化,所以遇到(例如)土耳其大写我的问题?
  • StringComparison.OrdinalIgnoreCase 的方法无法解决忽略新行的问题。
【解决方案4】:

首先通过正则表达式替换两个字符串中的所有空格,然后使用String.Compare 方法,参数为ignoreCase = true。

string a = System.Text.RegularExpressions.Regex.Replace("void foo", @"\s", "");
string b = System.Text.RegularExpressions.Regex.Replace("voidFoo", @"\s", "");
bool isTheSame = String.Compare(a, b, true) == 0;

【讨论】:

    【解决方案5】:

    如果您需要性能,此页面上的正则表达式解决方案对您来说运行速度太慢。也许您有一大串要排序的字符串。 (不过,Regex 解决方案更具可读性)

    我有一个类,它查看两个字符串中的每个单独的字符,并在忽略大小写和空格的同时比较它们。它不分配任何新字符串。它使用char.IsWhiteSpace(ch) 来确定空格,并使用char.ToLowerInvariant(ch) 来区分大小写(如果需要)。在我的测试中,我的解决方案的运行速度比基于正则表达式的解决方案快 5 到 8 倍。我的班级还在另一个 SO 答案中使用 this code 实现了 IEqualityComparer 的 GetHashCode(obj) 方法。这个GetHashCode(obj) 也忽略空格,并且可以选择忽略大小写。

    这是我的课:

    private class StringCompIgnoreWhiteSpace : IEqualityComparer<string>
    {
        public bool Equals(string strx, string stry)
        {
            if (strx == null) //stry may contain only whitespace
                return string.IsNullOrWhiteSpace(stry);
    
            else if (stry == null) //strx may contain only whitespace
                return string.IsNullOrWhiteSpace(strx);
    
            int ix = 0, iy = 0;
            for (; ix < strx.Length && iy < stry.Length; ix++, iy++)
            {
                char chx = strx[ix];
                char chy = stry[iy];
    
                //ignore whitespace in strx
                while (char.IsWhiteSpace(chx) && ix < strx.Length)
                {
                    ix++;
                    chx = strx[ix];
                }
    
                //ignore whitespace in stry
                while (char.IsWhiteSpace(chy) && iy < stry.Length)
                {
                    iy++;
                    chy = stry[iy];
                }
    
                if (ix == strx.Length && iy != stry.Length)
                { //end of strx, so check if the rest of stry is whitespace
                    for (int iiy = iy + 1; iiy < stry.Length; iiy++)
                    {
                        if (!char.IsWhiteSpace(stry[iiy]))
                            return false;
                    }
                    return true;
                }
    
                if (ix != strx.Length && iy == stry.Length)
                { //end of stry, so check if the rest of strx is whitespace
                    for (int iix = ix + 1; iix < strx.Length; iix++)
                    {
                        if (!char.IsWhiteSpace(strx[iix]))
                            return false;
                    }
                    return true;
                }
    
                //The current chars are not whitespace, so check that they're equal (case-insensitive)
                //Remove the following two lines to make the comparison case-sensitive.
                chx = char.ToLowerInvariant(chx);
                chy = char.ToLowerInvariant(chy);
    
                if (chx != chy)
                    return false;
            }
    
            //If strx has more chars than stry
            for (; ix < strx.Length; ix++)
            {
                if (!char.IsWhiteSpace(strx[ix]))
                    return false;
            }
    
            //If stry has more chars than strx
            for (; iy < stry.Length; iy++)
            {
                if (!char.IsWhiteSpace(stry[iy]))
                    return false;
            }
    
            return true;
        }
    
        public int GetHashCode(string obj)
        {
            if (obj == null)
                return 0;
    
            int hash = 17;
            unchecked // Overflow is fine, just wrap
            {
                for (int i = 0; i < obj.Length; i++)
                {
                    char ch = obj[i];
                    if(!char.IsWhiteSpace(ch))
                        //use this line for case-insensitivity
                        hash = hash * 23 + char.ToLowerInvariant(ch).GetHashCode();
    
                        //use this line for case-sensitivity
                        //hash = hash * 23 + ch.GetHashCode();
                }
            }
            return hash;
        }
    }
    

    private static void TestComp()
    {
        var comp = new StringCompIgnoreWhiteSpace();
    
        Console.WriteLine(comp.Equals("abcd", "abcd")); //true
        Console.WriteLine(comp.Equals("abCd", "Abcd")); //true
        Console.WriteLine(comp.Equals("ab Cd", "Ab\n\r\tcd   ")); //true
        Console.WriteLine(comp.Equals(" ab Cd", "  A b" + Environment.NewLine + "cd ")); //true
        Console.WriteLine(comp.Equals(null, "  \t\n\r ")); //true
        Console.WriteLine(comp.Equals("  \t\n\r ", null)); //true
        Console.WriteLine(comp.Equals("abcd", "abcd   h")); //false
    
        Console.WriteLine(comp.GetHashCode(" a b c d")); //-699568861
    
    
        //This is -699568861 if you #define StringCompIgnoreWhiteSpace_CASE_INSENSITIVE
        //  Otherwise it's -1555613149
        Console.WriteLine(comp.GetHashCode("A B c      \t       d"));
    }
    

    这是我的测试代码(带有正则表达式示例):

    private static void SpeedTest()
    {
        const int loop = 100000;
        string first = "a bc d";
        string second = "ABC D";
    
        var compChar = new StringCompIgnoreWhiteSpace();
        Stopwatch sw1 = Stopwatch.StartNew();
        for (int i = 0; i < loop; i++)
        {
            bool equals = compChar.Equals(first, second);
        }
        sw1.Stop();
        Console.WriteLine(string.Format("char time =  {0}", sw1.Elapsed)); //char time =  00:00:00.0361159
    
        var compRegex = new StringCompIgnoreWhiteSpaceRegex();
        Stopwatch sw2 = Stopwatch.StartNew();
        for (int i = 0; i < loop; i++)
        {
            bool equals = compRegex.Equals(first, second);
        }
        sw2.Stop();
        Console.WriteLine(string.Format("regex time = {0}", sw2.Elapsed)); //regex time = 00:00:00.2773072
    }
    
    private class StringCompIgnoreWhiteSpaceRegex : IEqualityComparer<string>
    {
        public bool Equals(string strx, string stry)
        {
            if (strx == null)
                return string.IsNullOrWhiteSpace(stry);
            else if (stry == null)
                return string.IsNullOrWhiteSpace(strx);
    
            string a = System.Text.RegularExpressions.Regex.Replace(strx, @"\s", "");
            string b = System.Text.RegularExpressions.Regex.Replace(stry, @"\s", "");
            return String.Compare(a, b, true) == 0;
        }
    
        public int GetHashCode(string obj)
        {
            if (obj == null)
                return 0;
    
            string a = System.Text.RegularExpressions.Regex.Replace(obj, @"\s", "");
            return a.GetHashCode();
        }
    }
    

    【讨论】:

    • 我为这个函数编写了单元测试,并发现了它在越界失败的极端情况。 ix &lt; strx.Length(和iy &lt; stry.Length相同)应该是ix &lt; strx.Length - 1。
    • 提示:这个方法是正确的,但是它太复杂了。去掉main for循环上的所有条件,在每个循环开始时将两个字符变量设置为空格(而不是当前字符串索引字符),如果两个字符在main for循环结束时都是空格,字符串匹配。其他循环和条件完全不需要。
    • @BryceWagner 我放弃了。我无法弄清楚您暗示的解决方案。为什么不单独回答这个问题?
    【解决方案6】:

    我可能会先从字符串中删除不想比较的字符,然后再进行比较。如果性能是一个问题,您可能会考虑存储已删除字符的每个字符串的版本。

    或者,您可以编写一个比较例程来跳过您想要忽略的字符。但这对我来说似乎更多的工作。

    【解决方案7】:

    你也可以使用下面的自定义函数

    public static string ExceptChars(this string str, IEnumerable<char> toExclude)
            {
                StringBuilder sb = new StringBuilder();
                for (int i = 0; i < str.Length; i++)
                {
                    char c = str[i];
                    if (!toExclude.Contains(c))
                        sb.Append(c);
                }
                return sb.ToString();
            }
    
            public static bool SpaceCaseInsenstiveComparision(this string stringa, string stringb)
            {
                return (stringa==null&&stringb==null)||stringa.ToLower().ExceptChars(new[] { ' ', '\t', '\n', '\r' }).Equals(stringb.ToLower().ExceptChars(new[] { ' ', '\t', '\n', '\r' }));
            }
    

    然后按照下面的方式使用

    "Te  st".SpaceCaseInsenstiveComparision("Te st");
    

    【讨论】:

    • 我会避免 .ToLower() 调用(因为它们会创建另一个字符串)并使用 StringComparison.OrdinalIgnoreCase(这也更快)。
    【解决方案8】:

    另一种选择是 LINQ SequenceEquals 方法,根据我的测试,它的速度是其他答案中使用的 Regex 方法的两倍多,并且非常易于阅读和维护。

    public static bool Equals_Linq(string s1, string s2)
    {
        return Enumerable.SequenceEqual(
            s1.Where(c => !char.IsWhiteSpace(c)).Select(char.ToUpperInvariant),
            s2.Where(c => !char.IsWhiteSpace(c)).Select(char.ToUpperInvariant));
    }
    
    public static bool Equals_Regex(string s1, string s2)
    {
        return string.Equals(
            Regex.Replace(s1, @"\s", ""),
            Regex.Replace(s2, @"\s", ""),
            StringComparison.OrdinalIgnoreCase);
    }
    

    这里是我使用的简单性能测试代码:

    var s1 = "HeLLo    wOrld!";
    var s2 = "Hello\n    WORLd!";
    var watch = Stopwatch.StartNew();
    for (var i = 0; i < 1000000; i++)
    {
        Equals_Linq(s1, s2);
    }
    Console.WriteLine(watch.Elapsed); // ~1.7 seconds
    watch = Stopwatch.StartNew();
    for (var i = 0; i < 1000000; i++)
    {
        Equals_Regex(s1, s2);
    }
    Console.WriteLine(watch.Elapsed); // ~4.6 seconds
    

    【讨论】:

      【解决方案9】:

      一种方法不是针对性能进行优化,而是针对完整性进行优化。

      • 标准化null
      • 规范化 unicode、组合字符、变音符号
      • 规范化新行
      • 标准化空白
      • 规范化套管

      代码sn-p:

      public static class StringHelper
      {
          public static bool AreEquivalent(string source, string target)
          {
              if (source == null) return target == null;
              if (target == null) return false;
              var normForm1 = Normalize(source);
              var normForm2 = Normalize(target);
              return string.Equals(normForm1, normForm2);
          }
      
          private static string Normalize(string value)
          {
              Debug.Assert(value != null);
              // normalize unicode, combining characters, diacritics
              value = value.Normalize(NormalizationForm.FormC);
              // normalize new lines to white space
              value = value.Replace("\r\n", "\n").Replace("\r", "\n");
              // normalize white space
              value = Regex.Replace(value, @"\s", string.Empty);
              // normalize casing
              return value.ToLowerInvariant();
          }
      }
      

      【讨论】:

      • 这将导致“1 2 3”和“123”的误报。我不认为这个问题意味着......
      【解决方案10】:
      1. 我将使用Trim() 修剪字符串以删除所有
        空格。
      2. 使用StringComparison.OrdinalIgnoreCase 忽略大小写敏感,例如。 stringA.Equals(stringB, StringComparison.OrdinalIgnoreCase)

      【讨论】:

      • Trim() 只在开始和结束时删除
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-08-28
      • 2021-10-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多