【问题标题】:Removing all whitespace lines from a multi-line string efficiently有效地从多行字符串中删除所有空白行
【发布时间】:2010-05-19 13:26:22
【问题描述】:

在 C# 中,删除空行(即仅包含字符串中的空格的行)的最佳方法是什么?如果这是最好的解决方案,我很乐意使用正则表达式。

编辑:我应该补充一下,我使用的是 .NET 2.0。


赏金更新:我会在赏金被授予后回滚,但我想澄清一些事情。

首先,任何 Perl 5 兼容的正则表达式都可以使用。这不仅限于 .NET 开发人员。标题和标签已被编辑以反映这一点。

其次,虽然我在赏金细节中给出了一个简单的示例,但这并不是您必须满足的唯一测试。您的解决方案必须删除所有行,这些行只包含空格,以及最后一个换行符。如果有一个字符串在运行您的正则表达式后以“/r/n”或任何空白字符结尾,它会失败。

【问题讨论】:

  • 正则表达式既快速又简单。当你说“最好的方法”时,你试图优化哪个方面?可读性?时间?内存使用?
  • 我想说在这种情况下可读性是最重要的。
  • 可读性很少等同于正则表达式
  • 同意他们可能会变得很毛茸茸,但我认为例如 Chris Schmich 的那个很好。

标签: c# regex string


【解决方案1】:

如果要删除包含任何空格(制表符、空格)的行,请尝试:

string fix = Regex.Replace(original, @"^\s*$\n", string.Empty, RegexOptions.Multiline);

编辑(针对@Will):修剪尾随换行符的最简单解决方案是在结果字符串上使用TrimEnd,例如:

string fix =
    Regex.Replace(original, @"^\s*$\n", string.Empty, RegexOptions.Multiline)
         .TrimEnd();

【讨论】:

  • 我认为 \s+ 而不是 \s* 会更好
  • @Salman Chris 的 rx 是正确的,我的孤独、不受欢迎的答案也是如此。 ;-(
  • @Salman A:\s+ 不适用于完全空行,例如"foo\n\nbar".
  • 这行得通,但它会在最后留下一个无关的换行符。
  • @ChrisSchmich:是的,纯粹是正则表达式。当内存中有几个 100mb 的字符串时,您不想创建仅相差“/r/n”的新实例。如果我能一口气搞定,我可以在记忆压力上放松一点。
【解决方案2】:
string outputString;
using (StringReader reader = new StringReader(originalString)
using (StringWriter writer = new StringWriter())
{
    string line;
    while((line = reader.ReadLine()) != null)
    {
        if (line.Trim().Length > 0)
            writer.WriteLine(line);
    }
    outputString = writer.ToString();
}

【讨论】:

  • +1 这个很好,因为它可以很好地适应大字符串。
  • 不应该是if (line.Trim().Length > 0) writer.WriteLine(line)吗? OP 没有要求在输出字符串中修剪所有行。
【解决方案3】:

在我的头顶上......

string fixed = Regex.Replace(input, "\s*(\n)","$1");

变成这样:

fdasdf 自卫队 [标签] [空格] 自卫队

进入这个:

fdasdf 自卫队 自卫队

【讨论】:

  • 什么?!不喜欢优雅的正则表达式?我被压垮了。
  • 有几种不同的方式来编写这个正则表达式,但我认为正则表达式的方法是最易读的。
  • +1。确实优雅。它还会从非空行的末尾删除制表符和空格,但这可能是一件好事。不过,您不需要 Multiline 选项。
  • @Alan - 你是对的。这是一个满足要求的快速即兴演奏。感谢您的提醒。
  • 如果文本以几个空行“\r\n\r\nfailure!”开头,这实际上会失败
【解决方案4】:

使用 LINQ:

var result = string.Join("\r\n",
                 multilineString.Split(new string[] { "\r\n" }, ...None)
                                .Where(s => !string.IsNullOrWhitespace(s)));

如果您正在处理大量输入和/或不一致的行尾,您应该使用 StringReader 并使用 foreach 循环来执行上述老式方法。

【讨论】:

  • 啊,它确实藏在那里。在这种情况下,除非您使用的是 .NET 4.0,否则您需要一个 .ToArray()。在我看来,这远不如正则表达式的可读性,而且我不确定您在这种方法中真正获得了什么。
  • LINQ 何时成为新的正则表达式?
  • 我最近使用 Linq 为我的冰箱解冻。既然 Linq 这么酷,为什么还要用老办法呢?
  • 为什么没有 Environment.NewLine 以及当 RemoveEmptyEntries 做同样的事情时为什么还要打扰 linq?
  • @Will: Environment.NewLine Channel 其值取决于平台,如果输入字符串包含 \r\n 换行符,这可能是不受欢迎的。 RemoveEmptyEntries 仅删除空条目,但不删除包含一个或多个空格字符的条目。
【解决方案5】:

好吧,这个答案符合赏金中明确的要求:

我还需要删除任何尾随换行符,我的 Regex-fu 是 失败。我的赏金给任何能给我一个通过的正则表达式的人 这个测试: StripWhitespace("test\r\n \r\nthis\r\n\r\n") == “测试\r\n这个”

答案如下:

(?<=\r?\n)(\s*$\r?\n)+|(?<=\r?\n)(\r?\n)+|(\r?\n)+\z

或者在@Chris Schmich提供的C#代码中:

string fix = Regex.Replace("test\r\n \r\nthis\r\n\r\n", @"(?<=\r?\n)(\s*$\r?\n)+|(?<=\r?\n)(\r?\n)+|(\r?\n)+\z", string.Empty, RegexOptions.Multiline);

现在让我们试着理解它。这里有三个可选模式,我愿意用string.empty 替换。

  1. (?&lt;=\r?\n)(\s*$\r?\n)+ - 匹配仅包含空格且前面有换行符的不限行(但不匹配前面的第一个换行符)。
  2. (?&lt;=\r?\n)(\r?\n)+ - 匹配一个到无限的空行,没有内容,前面有换行符(但不匹配前面的第一个换行符)。
  3. (\r?\n)+\z - 在测试字符串的末尾匹配一个到无限的换行符(你称之为尾随换行符)

这完全满足您的测试!但也满足\r\n\n 换行样式!测试一下!我相信这将是最正确的答案,虽然更简单的表达式会通过您指定的赏金测试,但这个正则表达式会通过更复杂的条件。

编辑: @Will 指出了上述正则表达式的最后一个模式匹配中的一个潜在缺陷,即它不会匹配测试字符串末尾包含空格的多个换行符。所以让我们把最后一个模式改成这样:

\b\s+\z \b 是单词边界(单词的开头或结尾),\s+ 是一个或多个空格字符,\z 是测试字符串的结尾(“文件”的结尾) .所以现在它将匹配文件末尾的任何种类的空格,包括制表符和空格,以及回车和换行符。我测试了@Will 提供的两个测试用例。

所以现在大家在一起,应该是:

(?<=\r?\n)(\s*$\r?\n)+|(?<=\r?\n)(\r?\n)+|\b\s+\z

编辑#2:好吧,还有一个可能的情况@Wil 发现最后一个正则表达式没有涵盖。这种情况是输入在文件开头的任何内容之前都有换行符。所以让我们再添加一个模式来匹配文件的开头。

\A\s+ - \A 匹配文件的开头,\s+ 匹配一个或多个空白字符。

所以现在我们得到了:

\A\s+|(?<=\r?\n)(\s*$\r?\n)+|(?<=\r?\n)(\r?\n)+|\b\s+\z

所以现在我们有四种匹配模式:

  1. 文件开头的空格,
  2. 包含空格的冗余换行符,(例如:\r\n \r\n\t\r\n
  3. 没有内容的冗余换行符,(例如:\r\n\r\n
  4. 文件末尾的空格

【讨论】:

  • @这是否应该通过单个 Regex.Replace 满足您的要求。
  • 哎呀,看起来工作量很大,但是当字符串末尾有混合换行符和空格时,它也会失败。比如这个字符串"one\r\n \r\ntwo\r\n\t\r\n \r\n"在替换之后就是"one\r\ntwo\r\n"
  • @我会做一个编辑来解决这个'bug'。正则表达式需要做很多工作,并没有被测试/标记为从字符串中删除行的最快方法,但这是您所要求的。一个单行正则表达式。
  • 嗯,当前编辑 "test\r\n \r\nthis\r\n\r\n" 在“test”和“this”之间留下一个空行。
  • @Will 这适用于我在 C#/.Net2 中进行测试。你在什么环境下运行? .Net 和 Perl 等之间的正则表达式存在一些细微的差异......我也许可以对其进行调整。
【解决方案6】:

不好。我会使用 JSON.net 来使用这个:

var o = JsonConvert.DeserializeObject(prettyJson);
new minifiedJson = JsonConvert.SerializeObject(o, Formatting.None);

【讨论】:

    【解决方案7】:

    为了响应 Will 的赏金,它期望一个采用 "test\r\n \r\nthis\r\n\r\n" 并输出 "test\r\nthis" 的解决方案,我想出了一个使用 atomic grouping(在 MSDN 上又名 Nonbacktracking Subexpressions)的解决方案。我建议阅读这些文章,以便更好地了解正在发生的事情。最终,原子组有助于匹配以其他方式留下的尾随换行符。

    RegexOptions.Multiline 与此模式一起使用:

    ^\s+(?!\B)|\s*(?>[\r\n]+)$
    

    这是一个包含一些测试用例的示例,包括我从 Will 在其他帖子上的 cmets 以及我自己的帖子中收集的一些。

    string[] inputs = 
    {
        "one\r\n \r\ntwo\r\n\t\r\n \r\n",
        "test\r\n \r\nthis\r\n\r\n",
        "\r\n\r\ntest!",
        "\r\ntest\r\n ! test",
        "\r\ntest \r\n ! "
    };
    string[] outputs = 
    {
        "one\r\ntwo",
        "test\r\nthis",
        "test!",
        "test\r\n ! test",
        "test \r\n ! "
    };
    
    string pattern = @"^\s+(?!\B)|\s*(?>[\r\n]+)$";
    
    for (int i = 0; i < inputs.Length; i++)
    {
        string result = Regex.Replace(inputs[i], pattern, "",
                                      RegexOptions.Multiline);
        Console.WriteLine(result == outputs[i]);
    }
    

    编辑:为了解决模式无法清理混合使用空格和换行符的文本的问题,我在正则表达式的最后一个替换部分添加了\s*。我之前的模式是多余的,我意识到\s* 可以处理这两种情况。

    【讨论】:

    • 不错的尝试,但并不完美。在字符串末尾附近混合空格和换行符时失败。 "one\r\n \r\ntwo\r\n\t\r\n \r\n" 末尾仍会有换行符。
    • @Will 感谢您的反馈。我已经更新了模式和示例代码来解决新的测试用例。试试看。我还清理了关于空间被占用的帖子,并选择将(?!\B) 部分保留在^\s+(?!\B) 中,因为我认为这更接近请求的精神并保留存在有效字符的空间。
    • 啊,好多了。今天我将花一些时间(样式化和)分析和运行测试用例。谢谢。
    • 在最终测试中。您的正则表达式是迄今为止最好的,但我遇到的唯一问题是,如果最后一行有 whitespace ,它会删除所有这些,而不仅仅是最后一个换行符。换句话说,"test\s\r\ntest\s\r\n" 返回"test\s\r\ntest"。我有 uploaded a simple app 用于验证和运行性能测试,如果你想再试一次。
    • @今晚我会试着看看。我稍微更新了模式以缩短它,但它并没有做任何新的事情来解决您的最后评论。
    【解决方案8】:
    string corrected = 
        System.Text.RegularExpressions.Regex.Replace(input, @"\n+", "\n");
    

    【讨论】:

    • 如果该行包含要删除的空白字符,您可以将 @"\n+" 更改为 @"\n\s?\n+"
    【解决方案9】:

    我会去的:

      public static string RemoveEmptyLines(string value) {
        using (StringReader reader = new StringReader(yourstring)) {
          StringBuilder builder = new StringBuilder();
          string line;
          while ((line = reader.ReadLine()) != null) {
            if (line.Trim().Length > 0)
              builder.AppendLine(line);
          }
          return builder.ToString();
        }
      }
    

    【讨论】:

      【解决方案10】:

      这是另一个选择:使用StringReader 类。优点:一次遍历字符串,不创建中间数组。

      public static string RemoveEmptyLines(this string text) {
          var builder = new StringBuilder();
      
          using (var reader = new StringReader(text)) {
              while (reader.Peek() != -1) {
                  string line = reader.ReadLine();
                  if (!string.IsNullOrWhiteSpace(line))
                      builder.AppendLine(line);
              }
          }
      
          return builder.ToString();
      }
      

      注意:IsNullOrWhiteSpace 方法是 new in .NET 4.0。如果你没有那个,你自己写是微不足道的:

      public static bool IsNullOrWhiteSpace(string text) {
          return string.IsNullOrEmpty(text) || text.Trim().Length < 1;
      }
      

      【讨论】:

      • @Adam:哈,哇,我在那里发表的非常愚蠢的声明。我的意思是没有中间 数组,因为 string.Split 方法会(谢谢)。
      【解决方案11】:

      作为对 Will 的赏金的回应,这里有一个 Perl 子程序,它对测试用例给出了正确的响应:

      sub StripWhitespace {
          my $str = shift;
          print "'",$str,"'\n";
          $str =~ s/(?:\R+\s+(\R)+)|(?:()\R+)$/$1/g;
          print "'",$str,"'\n";
          return $str;
      }
      StripWhitespace("test\r\n \r\nthis\r\n\r\n");
      

      输出:

      'test
      
      this
      
      '
      'test
      this'
      

      为了不使用\R,请将其替换为[\r\n] 并反转替代方案。这个产生相同的结果:

      $str =~ s/(?:(\S)[\r\n]+)|(?:[\r\n]+\s+([\r\n])+)/$1/g;
      

      不需要特殊配置,也不需要多线支持。不过,如果是强制性的,您可以添加 s 标志。

      $str =~ s/(?:(\S)[\r\n]+)|(?:[\r\n]+\s+([\r\n])+)/$1/sg;
      

      【讨论】:

      • 呃,我可以使用 Perl 兼容的正则表达式...但我不熟悉 Perl。你能澄清一下正则表达式是什么吗?我想我猜对了,但我想确定一下。谢谢。 (编辑)嗯,是的,例如我刚刚了解了 s/ 运算符。此外,如果需要任何配置选项(多行等)(编辑编辑)它必须是 PCRE 5; 7不会剪。 \R 太新了。
      • 嗯,我似乎无法让它工作。它确实删除了空行和任何尾随的换行符,但它也会裁剪每行的最后一个非空白字符。转换可能仍然存在问题。你有没有机会在没有任何 perl 语法的情况下给我正则表达式?
      【解决方案12】:

      如果它只有空格,为什么不使用 C# 字符串方法

          string yourstring = "A O P V 1.5";
          yourstring.Replace("  ", string.empty);
      

      结果将是“AOPV1.5”

      【讨论】:

        【解决方案13】:
        char[] delimiters = new char[] { '\r', '\n' };
        string[] lines = value.Split(delimiters, StringSplitOptions.RemoveEmptyEntries);
        string result = string.Join(Environment.NewLine, lines)
        

        【讨论】:

        • 如何处理大文件?
        【解决方案14】:

        如果针对每一行工作,这很简单......

        (^\s+|\s+|^)$
        

        【讨论】:

          【解决方案15】:

          嗯。好吧,毕竟,我找不到一个能解决我能想到的所有角落的问题。以下是我对剥离

          的正则表达式的最新咒语
          1. 字符串开头的所有空行
            • 在第一个非空白行的开头不包括任何空格
          2. 第一个非空白行之后和最后一个非空白行之前的所有空行
            • 同样,保留所有非空白行开头的空白
          3. 最后一个非空白行之后的所有空行,包括最后一个换行符

          (?

          本质上说:

          • 紧接着
            • 字符串的开头或
            • 最后一行的结尾
          • 尽可能多地匹配以换行符结尾的连续空格*
          • 匹配换行符和尽可能多的连续空格,在字符串末尾结束

          前半部分捕获字符串开头的所有空白,直到第一个非空白行,或非空白行之间的所有空白。后半部分捕获字符串中剩余的空白,包括最后一个非空白行的换行符。

          感谢所有试图提供帮助的人;您的回答帮助我思考了匹配时需要考虑的一切。

          *(此正则表达式认为换行符为\r\n,因此必须根据字符串的来源进行调整。无需设置任何选项即可运行匹配。)

          【讨论】:

            【解决方案16】:

            字符串扩展

            public static string UnPrettyJson(this string s)
            {
                try
                {
                    // var jsonObj = Json.Decode(s);
                    // var sObject = Json.Encode(value);   dont work well with array of strings c:['a','b','c']
            
                    object jsonObj = JsonConvert.DeserializeObject(s);
                    return JsonConvert.SerializeObject(jsonObj, Formatting.None);
                }
                catch (Exception e)
                {
                    throw new Exception(
                        s + " Is Not a valid JSON ! (please validate it in http://www.jsoneditoronline.org )", e);
                }
            }
            

            【讨论】:

              【解决方案17】:

              我不确定它是否有效,但是 =)

                List<string> strList = myString.Split(new string[] { "\n" }, StringSplitOptions.None).ToList<string>();
                myString = string.Join("\n", strList.Where(s => !string.IsNullOrWhiteSpace(s)).Distinct().ToList());
              

              【讨论】:

                【解决方案18】:

                试试这个。

                string s = "Test1" + Environment.NewLine + Environment.NewLine + "Test 2";
                Console.WriteLine(s);
                
                string result = s.Replace(Environment.NewLine, String.Empty);
                Console.WriteLine(result);
                

                【讨论】:

                • 如果我正在读取从 unix 系统导入的文件,那么我的 windows Environment.NewLine 不会匹配文件中的新行。
                【解决方案19】:
                s = Regex.Replace(s, @"^[^\n\S]*\n", "");
                

                [^\n\S] 匹配任何不是换行符或非空白字符的字符——因此,除了\n 之外的任何空白字符。但很可能您需要担心的唯一字符是空格、制表符和回车,所以这也应该可以:

                s = Regex.Replace(s, @"^[ \t\r]*\n", "");
                

                如果你想让它在没有最后换行的情况下捕捉最后一行:

                s = Regex.Replace(s, @"^[ \t\r]*\n?", "");
                

                【讨论】:

                • 这些都不起作用。他们都留下空白行。
                猜你喜欢
                • 1970-01-01
                • 2018-03-04
                • 2011-08-27
                • 2012-10-29
                • 2022-03-07
                • 1970-01-01
                • 2019-11-25
                • 1970-01-01
                • 2011-06-16
                相关资源
                最近更新 更多