【问题标题】:How to not include line breaks when comparing two strings比较两个字符串时如何不包括换行符
【发布时间】:2010-08-06 10:44:16
【问题描述】:

我正在比较两个字符串的更新。我做了一个:

 string1 != string2

结果不同。我把它们放在“添加手表”中,我看到唯一的区别是一个有换行符,另一个没有。:

 string1 = "This is a test. \nThis is a test";
 string2 = "This is a test. This is a test";

我基本上想进行比较,但不包括换行符。因此,如果换行符是唯一的区别,则认为它们相等。

【问题讨论】:

  • 从技术上讲,这 2 个字符串 不同的,并且在屏幕上或打印它们时会以不同的方式显示。也就是说,提供的任何答案当然都可以满足您的需求。

标签: c# string comparison


【解决方案1】:

当性能不是什么大问题时,一种快速而肮脏的方式:

string1.Replace("\n", "") != string2.Replace("\n", "")

【讨论】:

  • 这个答案没有考虑\r这个字符。
  • 扩展对性能的引用,这里的潜在问题是每次比较都涉及在堆上分配每个字符串的副本。如果性能很重要,请查看my answer
【解决方案2】:

我建议正则表达式将每个 spacetab\r\n 减少到一个空格:

Regex.Replace(string1, @"\s+", " ") != Regex.Replace(string2, @"\s+", " ")

【讨论】:

  • 但这会删除空格和制表符 - 我认为 OP 只想忽略换行符
  • 是的,正如我所说的那样,但我不明白为什么当新行不相关时多个空格很重要
  • @Didistis:碰巧我有一个用例正好需要它——Visual Studio 扩展的智能缩进提供程序的单元测试(缩进很重要,但行尾不重要):- )
【解决方案3】:

假设:

  1. != 和 == 的直接字符值对字符值比较是这里想要的,除了换行符的问题。
  2. 字符串足够大,或者可能足够大,或者比较频繁,以至于仅仅用空字符串替换 "\n" 效率太低了。

然后:

public bool LinelessEquals(string x, string y)
{
    //deal with quickly handlable cases quickly.
    if(ReferenceEquals(x, y))//same instance
        return true;         // - generally happens often in real code,
                             //and is a fast check, so always worth doing first.
    //We already know they aren't both null as
    //ReferenceEquals(null, null) returns true.
    if(x == null || y == null)
        return false;
    IEnumerator<char> eX = x.Where(c => c != '\n').GetEnumerator();
    IEnumerator<char> eY = y.Where(c => c != '\n').GetEnumerator();
    while(eX.MoveNext())
    {
        if(!eY.MoveNext()) //y is shorter
            return false;
        if(ex.Current != ey.Current)
            return false;
    }
    return !ey.MoveNext(); //check if y was longer.
}

这被定义为相等而不是不相等,因此您可以轻松地将其修改为IEqualityComparer&lt;string&gt;.Equals 的实现。您对无换行符string1 != string2 的问题变为:!LinelessEquals(string1, string2)

【讨论】:

  • 让我们看看,+1 用于花时间提供一个好的答案,+1 用于检查引用相等和 null 的第一部分,-1 用于字符串比较方法中的 linq 过度杀伤。整体 +1 :)
  • 我把 lambda 放在那里主要是因为它使代码更紧凑,而且它是一个非常直接的表达式,可以相当快地执行。老式的方法是添加一个辅助函数,该函数 yield 返回不是换行符的字符。真正老式的(.NET 1.1)方式要么有一个辅助类,要么在它的迭代中要复杂得多。在字符串比较方法中很难有矫枉过正,通常字符串比较确实非常复杂(考虑在无大小写比较中 ß 匹配 SS 的常见情况)。
【解决方案4】:

更简洁的方法是使用:

string1.Replace(Environment.NewLine, String.Empty) != string2.Replace(Environment.NewLine, String.Empty);

【讨论】:

    【解决方案5】:

    这是一个忽略某些字符的字符串相等比较器,例如 \r\n

    此实现在执行期间不分配任何堆内存,有助于提高性能。它还可以避免通过IEnumerableIEnumerator 进行虚拟通话。

    public sealed class SelectiveStringComparer : IEqualityComparer<string>
    {
        private readonly string _ignoreChars;
    
        public SelectiveStringComparer(string ignoreChars = "\r\n")
        {
            _ignoreChars = ignoreChars;
        }
    
        public bool Equals(string x, string y)
        {
            if (ReferenceEquals(x, y))
                return true;
            if (x == null || y == null)
                return false;
            var ix = 0;
            var iy = 0;
            while (true)
            {
                while (ix < x.Length && _ignoreChars.IndexOf(x[ix]) != -1)
                    ix++;
                while (iy < y.Length && _ignoreChars.IndexOf(y[iy]) != -1)
                    iy++;
                if (ix >= x.Length)
                    return iy >= y.Length;
                if (iy >= y.Length)
                    return false;
                if (x[ix] != y[iy])
                    return false;
                ix++;
                iy++;
            }
        }
    
        public int GetHashCode(string obj)
        {
            throw new NotSupportedException();
        }
    }
    

    【讨论】:

      【解决方案6】:

      这是 Jon Hannas 答案的通用且经过测试的版本。

      /// <summary>
      /// Compares two character enumerables one character at a time, ignoring those specified.
      /// </summary>
      /// <param name="x"></param>
      /// <param name="y"></param>
      /// <param name="ignoreThese"> If not specified, the default is to ignore linefeed and newline: {'\r', '\n'} </param>
      /// <returns></returns>
      public static bool EqualsIgnoreSome(this IEnumerable<char> x, IEnumerable<char> y, params char[] ignoreThese)
      {
          // First deal with quickly handlable cases quickly:
          // Same instance - generally happens often in real code, and is a fast check, so always worth doing first.
          if (ReferenceEquals(x, y))
              return true;         //
          // We already know they aren't both null as ReferenceEquals(null, null) returns true.
          if (x == null || y == null)
              return false;
          // Default ignore is newlines:
          if (ignoreThese == null || ignoreThese.Length == 0)
              ignoreThese = new char[] { '\r', '\n' };
          // Filters by specifying enumerator.
          IEnumerator<char> eX = x.Where(c => !ignoreThese.Contains(c)).GetEnumerator();
          IEnumerator<char> eY = y.Where(c => !ignoreThese.Contains(c)).GetEnumerator();
          // Compares.
          while (eX.MoveNext())
          {
              if (!eY.MoveNext()) //y is shorter
                  return false;
              if (eX.Current != eY.Current)
                  return false;
          }
          return !eY.MoveNext(); //check if y was longer.
      }
      

      【讨论】:

        【解决方案7】:
        string1.replace('\n','') != string2.replace('\n','')
        

        【讨论】:

          【解决方案8】:

          你不能在比较字符串之前去掉换行符吗?

          例如(伪代码)...

          string1.replace('\n','') != string2.replace('\n','')
          

          【讨论】:

          • @Paul Creasey:你打败了我!
          【解决方案9】:

          这是基于 Drew Noakes 答案的 VB.net 版本

          Dim g_sIgnore As String = vbSpace & vbNewLine & vbTab 'String.Format("\n\r\t ")
          
          Public Function StringCompareIgnoringWhitespace(s1 As String, s2 As String) As Boolean
              Dim i1 As Integer = 0
              Dim i2 As Integer = 0
              Dim s1l As Integer = s1.Length
              Dim s2l As Integer = s2.Length
          
              Do
                  While i1 < s1l AndAlso g_sIgnore.IndexOf(s1(i1)) <> -1
                      i1 += 1
                  End While
                  While i2 < s2l AndAlso g_sIgnore.IndexOf(s2(i2)) <> -1
                      i2 += 1
                  End While
                  If i1 = s1l And i2 = s2l Then
                      Return True
                  Else
                      If i1 < s1l AndAlso i2 < s2l AndAlso s1(i1) = s2(i2) Then
                          i1 += 1
                          i2 += 1
                      Else
                          Return False
                      End If
                  End If
              Loop
              Return False
          End Function
          

          我也测试过

          Try
              Debug.Assert(Not StringCompareIgnoringWhitespace("a", "z"))
              Debug.Assert(Not StringCompareIgnoringWhitespace("aa", "zz"))
              Debug.Assert(StringCompareIgnoringWhitespace("", ""))
              Debug.Assert(StringCompareIgnoringWhitespace(" ", ""))
              Debug.Assert(StringCompareIgnoringWhitespace("", " "))
              Debug.Assert(StringCompareIgnoringWhitespace(" a", "a "))
              Debug.Assert(StringCompareIgnoringWhitespace(" aa", "aa "))
              Debug.Assert(StringCompareIgnoringWhitespace(" aa ", " aa "))
              Debug.Assert(StringCompareIgnoringWhitespace(" aa a", " aa a"))
              Debug.Assert(Not StringCompareIgnoringWhitespace("a", ""))
              Debug.Assert(Not StringCompareIgnoringWhitespace("", "a"))
              Debug.Assert(Not StringCompareIgnoringWhitespace("ccc", ""))
              Debug.Assert(Not StringCompareIgnoringWhitespace("", "ccc"))
          Catch ex As Exception
              Console.WriteLine(ex.ToString)
          End Try
          

          【讨论】:

            【解决方案10】:

            当我编写需要将多行预期字符串与实际输出字符串进行比较的单元测试时,我已经多次遇到这个问题。

            例如,如果我正在编写一个输出多行字符串的方法,我关心每一行的外观,但我不关心 Windows 或 Mac 机器上使用的特定换行符。

            在我的例子中,我只想断言我的单元测试中的每一行都是相等的,如果其中一个不相等则退出。

            public static void AssertAreLinesEqual(string expected, string actual)
            {
                using (var expectedReader = new StringReader(expected))
                using (var actualReader = new StringReader(actual))
                {
                    while (true)
                    {
                        var expectedLine = expectedReader.ReadLine();
                        var actualLine = actualReader.ReadLine();
            
                        Assert.AreEqual(expectedLine, actualLine);
            
                        if(expectedLine == null || actualLine == null)
                            break;
                    }
                }
            }
            

            当然,您也可以使该方法更通用一点,并改为返回bool

            public static bool AreLinesEqual(string expected, string actual)
            {
                using (var expectedReader = new StringReader(expected))
                using (var actualReader = new StringReader(actual))
                {
                    while (true)
                    {
                        var expectedLine = expectedReader.ReadLine();
                        var actualLine = actualReader.ReadLine();
            
                        if (expectedLine != actualLine)
                            return false;
            
                        if(expectedLine == null || actualLine == null)
                            break;
                    }
                }
            
                return true;
            }
            

            最让我惊讶的是,我用过的任何单元测试框架中都没有这样的方法。

            【讨论】:

              【解决方案11】:

              我在单元测试中遇到了行尾问题。

              //compare files ignoring line ends
                  org.junit.Assert.assertEquals(
                          read.readPayload("myFile.xml")
                                  .replace("\n", "")
                                  .replace("\r", ""),
                          values.getFile()
                                  .replace("\n", "")
                                  .replace("\r", ""));
              

              我通常不喜欢进行这种比较(比较整个文件),因为更好的方法是验证字段。但它在这里回答了这个问题,因为它删除了大多数系统的行尾(replace 调用是诀窍)。

              PS:read.readPayload 从资源文件夹中读取一个文本文件并将其放入一个字符串中,values 是一个结构,其中包含一个字符串,其属性中包含文件的原始内容(作为字符串)。

              PS2:没有考虑性能,因为它只是单元测试的丑陋修复

              【讨论】:

                猜你喜欢
                • 2021-07-05
                • 2022-06-29
                • 2019-01-14
                • 1970-01-01
                • 1970-01-01
                • 2022-01-22
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多