【问题标题】:How to replace multiple white spaces with one white space如何用一个空格替换多个空格
【发布时间】:2010-11-19 18:34:49
【问题描述】:

假设我有一个字符串,例如:

"Hello     how are   you           doing?"

我想要一个将多个空格变成一个空格的函数。

所以我会得到:

"Hello how are you doing?"

我知道我可以使用正则表达式或调用

string s = "Hello     how are   you           doing?".replace("  "," ");

但我必须多次调用它以确保所有连续的空格都被替换为一个。

是否已经有一个内置的方法呢?

【问题讨论】:

  • 您能否澄清一下:您是只处理空格,还是“所有”空格?
  • 您是否希望将任何非空格空格转换为空格?
  • 我的意思是所有连续的空格最多应该是 1
  • 需要考虑的两件事:1. char.IsWhiteSpace 包括回车、换行等。 2. 使用 Char.GetUnicodeCategory(ch) = Globalization.UnicodeCategory.SpaceSeparator

标签: c# string whitespace


【解决方案1】:

最小的解决方案:

var regExp=/\s+/g,
newString=oldString.replace(regExp,' ');

【讨论】:

    【解决方案2】:
    string.Join(" ", s.Split(" ").Where(r => r != ""));
    

    【讨论】:

      【解决方案3】:

      A fast extra whitespace remover by Felipe Machado.(由RW修改,去除多空间)

      static string DuplicateWhiteSpaceRemover(string str)
      {
          var len = str.Length;
          var src = str.ToCharArray();
          int dstIdx = 0;
          bool lastWasWS = false; //Added line
          for (int i = 0; i < len; i++)
          {
              var ch = src[i];
              switch (ch)
              {
                  case '\u0020': //SPACE
                  case '\u00A0': //NO-BREAK SPACE
                  case '\u1680': //OGHAM SPACE MARK
                  case '\u2000': // EN QUAD
                  case '\u2001': //EM QUAD
                  case '\u2002': //EN SPACE
                  case '\u2003': //EM SPACE
                  case '\u2004': //THREE-PER-EM SPACE
                  case '\u2005': //FOUR-PER-EM SPACE
                  case '\u2006': //SIX-PER-EM SPACE
                  case '\u2007': //FIGURE SPACE
                  case '\u2008': //PUNCTUATION SPACE
                  case '\u2009': //THIN SPACE
                  case '\u200A': //HAIR SPACE
                  case '\u202F': //NARROW NO-BREAK SPACE
                  case '\u205F': //MEDIUM MATHEMATICAL SPACE
                  case '\u3000': //IDEOGRAPHIC SPACE
                  case '\u2028': //LINE SEPARATOR
                  case '\u2029': //PARAGRAPH SEPARATOR
                  case '\u0009': //[ASCII Tab]
                  case '\u000A': //[ASCII Line Feed]
                  case '\u000B': //[ASCII Vertical Tab]
                  case '\u000C': //[ASCII Form Feed]
                  case '\u000D': //[ASCII Carriage Return]
                  case '\u0085': //NEXT LINE
                      if (lastWasWS == false) //Added line
                      {
                          src[dstIdx++] = ' '; // Updated by Ryan
                          lastWasWS = true; //Added line
                      }
                      continue;
                  default:
                      lastWasWS = false; //Added line 
                      src[dstIdx++] = ch;
                      break;
              }
          }
          return new string(src, 0, dstIdx);
      }
      

      基准测试...

      |                           | Time  |   TEST 1    |   TEST 2    |   TEST 3    |   TEST 4    |   TEST 5    |
      | Function Name             |(ticks)| dup. spaces | spaces+tabs | spaces+CR/LF| " " -> " "  | " " -> " " |
      |---------------------------|-------|-------------|-------------|-------------|-------------|-------------|
      | SwitchStmtBuildSpaceOnly  |   5.2 |    PASS     |    FAIL     |    FAIL     |    PASS     |    PASS     |
      | InPlaceCharArraySpaceOnly |   5.6 |    PASS     |    FAIL     |    FAIL     |    PASS     |    PASS     |
      | DuplicateWhiteSpaceRemover|   7.0 |    PASS     |    PASS     |    PASS     |    PASS     |    PASS     |
      | SingleSpacedTrim          |  11.8 |    PASS     |    PASS     |    PASS     |    FAIL     |    FAIL     |
      | Fubo(StringBuilder)       |    13 |    PASS     |    FAIL     |    FAIL     |    PASS     |    PASS     |
      | User214147                |    19 |    PASS     |    PASS     |    PASS     |    FAIL     |    FAIL     | 
      | RegExWithCompile          |    28 |    PASS     |    FAIL     |    FAIL     |    PASS     |    PASS     |
      | SwitchStmtBuild           |    34 |    PASS     |    FAIL     |    FAIL     |    PASS     |    PASS     |
      | SplitAndJoinOnSpace       |    55 |    PASS     |    FAIL     |    FAIL     |    FAIL     |    FAIL     |
      | RegExNoCompile            |   120 |    PASS     |    PASS     |    PASS     |    PASS     |    PASS     |
      | RegExBrandon              |   137 |    PASS     |    FAIL     |    PASS     |    PASS     |    PASS     |
      

      基准测试说明:发布模式,未附加调试器,i7 处理器,平均 4 次运行,仅测试短字符串

      SwitchStmtBuildSpaceOnly by Felipe Machado 2015 并由 Sunsetquest 修改

      InPlaceCharArraySpaceOnly by Felipe Machado 2015 并由 Sunsetquest 修改

      SwitchStmtBuild by Felipe Machado 2015 并由 Sunsetquest 修改

      SwitchStmtBuild2 Felipe Machado 2015 由 Sunsetquest 修改

      SingleSpacedTrim by David S 2013

      Fubo(StringBuilder) by fubo 2014

      SplitAndJoinOnSpace by Jon Skeet 2009

      RegExWithCompile by Jon Skeet 2009

      user214147的用户214147

      RegExBrandon by Brandon

      RegExNoCompile by Tim Hoolihan

      Benchmark code is on Github

      【讨论】:

      • 很高兴在这里看到我的文章! (我是 Felipe Machado)我将使用名为 BenchmarkDotNet 的适当基准工具对其进行更新!我将尝试在所有运行时设置运行(现在我们有 DOT NET CORE 等......
      • @Loudenvier - 在这方面做得很好。你的速度最快,快了近 400%! .Net Core 就像免费的 150-200% 性能提升。它越来越接近 c++ 的性能,但更容易编码。感谢您的评论。
      • 这只是空格,而不是其他空白字符。也许你想要 char.IsWhiteSpace(ch) 而不是 src[i] == '\u0020'。我注意到这已被社区编辑。他们搞砸了吗?
      【解决方案4】:

      替换组提供了更简单的方法来解决用相同单个字符替换多个空白字符:

          public static void WhiteSpaceReduce()
          {
              string t1 = "a b   c d";
              string t2 = "a b\n\nc\nd";
      
              Regex whiteReduce = new Regex(@"(?<firstWS>\s)(?<repeatedWS>\k<firstWS>+)");
              Console.WriteLine("{0}", t1);
              //Console.WriteLine("{0}", whiteReduce.Replace(t1, x => x.Value.Substring(0, 1))); 
              Console.WriteLine("{0}", whiteReduce.Replace(t1, @"${firstWS}"));
              Console.WriteLine("\nNext example ---------");
              Console.WriteLine("{0}", t2);
              Console.WriteLine("{0}", whiteReduce.Replace(t2, @"${firstWS}"));
              Console.WriteLine();
          }
      

      请注意,第二个示例保持单个 \n,而接受的答案将用空格替换行尾。

      如果您需要用第一个替换任何空白字符组合,只需从模式中删除反向引用\k

      【讨论】:

        【解决方案5】:

        你可以试试这个:

            /// <summary>
            /// Remove all extra spaces and tabs between words in the specified string!
            /// </summary>
            /// <param name="str">The specified string.</param>
            public static string RemoveExtraSpaces(string str)
            {
                str = str.Trim();
                StringBuilder sb = new StringBuilder();
                bool space = false;
                foreach (char c in str)
                {
                    if (char.IsWhiteSpace(c) || c == (char)9) { space = true; }
                    else { if (space) { sb.Append(' '); }; sb.Append(c); space = false; };
                }
                return sb.ToString();
            }
        

        【讨论】:

          【解决方案6】:

          VB.NET

          Linha.Split(" ").ToList().Where(Function(x) x <> " ").ToArray
          

          C#

          Linha.Split(" ").ToList().Where(x => x != " ").ToArray();
          

          享受 LINQ 的力量 =D

          【讨论】:

          • 没错!对我来说,这也是最优雅的方法。所以为了记录,在 C# 中应该是:string.Join(" ", myString.Split(' ').Where(s =&gt; s != " ").ToArray())
          • Split 的小改进以捕获所有空格并删除Where 子句:myString.Split(null as char[], StringSplitOptions.RemoveEmptyEntries)
          【解决方案7】:

          这是我使用的解决方案。没有 RegEx 和 String.Split。

          public static string TrimWhiteSpace(this string Value)
          {
              StringBuilder sbOut = new StringBuilder();
              if (!string.IsNullOrEmpty(Value))
              {
                  bool IsWhiteSpace = false;
                  for (int i = 0; i < Value.Length; i++)
                  {
                      if (char.IsWhiteSpace(Value[i])) //Comparion with WhiteSpace
                      {
                          if (!IsWhiteSpace) //Comparison with previous Char
                          {
                              sbOut.Append(Value[i]);
                              IsWhiteSpace = true;
                          }
                      }
                      else
                      {
                          IsWhiteSpace = false;
                          sbOut.Append(Value[i]);
                      }
                  }
              }
              return sbOut.ToString();
          }
          

          所以你可以:

          string cleanedString = dirtyString.TrimWhiteSpace();
          

          【讨论】:

            【解决方案8】:

            使用 Jon Skeet 发布的测试程序,我尝试查看是否可以让手写循环运行得更快。
            我每次都可以击败 NormalizeWithSplitAndJoin,但只能以 1000、5 的输入击败 NormalizeWithRegex。

            static string NormalizeWithLoop(string input)
            {
                StringBuilder output = new StringBuilder(input.Length);
            
                char lastChar = '*';  // anything other then space 
                for (int i = 0; i < input.Length; i++)
                {
                    char thisChar = input[i];
                    if (!(lastChar == ' ' && thisChar == ' '))
                        output.Append(thisChar);
            
                    lastChar = thisChar;
                }
            
                return output.ToString();
            }
            

            我没有查看抖动产生的机器代码,但是我认为问题在于调用 StringBuilder.Append() 所花费的时间,并且要做得更好需要使用不安全的代码。

            所以 Regex.Replace() 非常快而且很难被击败!!

            【讨论】:

              【解决方案9】:
              string cleanedString = System.Text.RegularExpressions.Regex.Replace(dirtyString,@"\s+"," ");
              

              【讨论】:

              • imo,如果您对正则表达式感到满意,请避免过早优化
              • 如果您的应用程序不是时间紧迫的,它可以承受 1 微秒的处理开销。
              • 请注意,'\s' 不仅可以替换空格,还可以替换换行符。
              • 很好,如果您只想要空格,请将模式切换为“[ ]+”
              • 您不应该使用“{2,}”而不是“+”来避免替换单个空格吗?
              【解决方案10】:

              我正在分享我使用的东西,因为我似乎想出了一些不同的东西。我已经使用了一段时间,它对我来说已经足够快了。我不确定它如何与其他人相提并论。我在分隔文件编写器中使用它,并通过它一次运行一个字段的大型数据表。

                  public static string NormalizeWhiteSpace(string S)
                  {
                      string s = S.Trim();
                      bool iswhite = false;
                      int iwhite;
                      int sLength = s.Length;
                      StringBuilder sb = new StringBuilder(sLength);
                      foreach(char c in s.ToCharArray())
                      {
                          if(Char.IsWhiteSpace(c))
                          {
                              if (iswhite)
                              {
                                  //Continuing whitespace ignore it.
                                  continue;
                              }
                              else
                              {
                                  //New WhiteSpace
              
                                  //Replace whitespace with a single space.
                                  sb.Append(" ");
                                  //Set iswhite to True and any following whitespace will be ignored
                                  iswhite = true;
                              }  
                          }
                          else
                          {
                              sb.Append(c.ToString());
                              //reset iswhitespace to false
                              iswhite = false;
                          }
                      }
                      return sb.ToString();
                  }
              

              【讨论】:

                【解决方案11】:

                没有内置方法可以做到这一点。你可以试试这个:

                private static readonly char[] whitespace = new char[] { ' ', '\n', '\t', '\r', '\f', '\v' };
                public static string Normalize(string source)
                {
                   return String.Join(" ", source.Split(whitespace, StringSplitOptions.RemoveEmptyEntries));
                }
                

                这将删除前导和尾随空格,并将任何内部空格折叠为单个空格字符。如果你真的只想折叠空格,那么使用正则表达式的解决方案会更好;否则这个解决方案会更好。 (参见 Jon Skeet 的 analysis。)

                【讨论】:

                • 如果正则表达式被编译和缓存,我不确定它是否比拆分和连接有更多的开销,这可能会创建 loads 中间垃圾字符串。在假设您的方法更快之前,您是否对这两种方法进行了仔细的基准测试?
                • 此处未声明空格
                • 说到开销,你到底为什么打电话给source.ToCharArray()然后把结果扔掉?
                • And 在 string.Join 的结果上调用 ToCharArray(),只是为了创建一个新字符串...哇,因为在帖子中抱怨开销是非常了不起。 -1.
                • 哦,假设whitespacenew char[] { ' ' },如果输入字符串以空格开头或结尾,这将给出错误的结果。
                【解决方案12】:

                这个问题并不像其他发帖人所说的那么简单(而且我最初认为它是这样的)——因为这个问题并不像它需要的那样精确。

                “空格”和“空白”是有区别的。如果您 表示空格,那么您应该使用" {2,}" 的正则表达式。如果您的意思是 any 空格,那是另一回事。应该将 all 空格转换为空格吗?空间在开始和结束时应该发生什么?

                对于下面的基准,我假设您只关心空格,并且您不想对单个空格做任何事情,即使在开始和结束时也是如此。

                请注意,正确性几乎总是比性能更重要。就您指定的要求而言,Split/Join 解决方案删除任何前导/尾随空格(甚至只是单个空格)这一事实是不正确的(当然,这可能是不完整的)。

                基准测试使用MiniBench

                using System;
                using System.Text.RegularExpressions;
                using MiniBench;
                
                internal class Program
                {
                    public static void Main(string[] args)
                    {
                
                        int size = int.Parse(args[0]);
                        int gapBetweenExtraSpaces = int.Parse(args[1]);
                
                        char[] chars = new char[size];
                        for (int i=0; i < size/2; i += 2)
                        {
                            // Make sure there actually *is* something to do
                            chars[i*2] = (i % gapBetweenExtraSpaces == 1) ? ' ' : 'x';
                            chars[i*2 + 1] = ' ';
                        }
                        // Just to make sure we don't have a \0 at the end
                        // for odd sizes
                        chars[chars.Length-1] = 'y';
                
                        string bigString = new string(chars);
                        // Assume that one form works :)
                        string normalized = NormalizeWithSplitAndJoin(bigString);
                
                
                        var suite = new TestSuite<string, string>("Normalize")
                            .Plus(NormalizeWithSplitAndJoin)
                            .Plus(NormalizeWithRegex)
                            .RunTests(bigString, normalized);
                
                        suite.Display(ResultColumns.All, suite.FindBest());
                    }
                
                    private static readonly Regex MultipleSpaces = 
                        new Regex(@" {2,}", RegexOptions.Compiled);
                
                    static string NormalizeWithRegex(string input)
                    {
                        return MultipleSpaces.Replace(input, " ");
                    }
                
                    // Guessing as the post doesn't specify what to use
                    private static readonly char[] Whitespace =
                        new char[] { ' ' };
                
                    static string NormalizeWithSplitAndJoin(string input)
                    {
                        string[] split = input.Split
                            (Whitespace, StringSplitOptions.RemoveEmptyEntries);
                        return string.Join(" ", split);
                    }
                }
                

                一些测试运行:

                c:\Users\Jon\Test>test 1000 50
                ============ Normalize ============
                NormalizeWithSplitAndJoin  1159091 0:30.258 22.93
                NormalizeWithRegex        26378882 0:30.025  1.00
                
                c:\Users\Jon\Test>test 1000 5
                ============ Normalize ============
                NormalizeWithSplitAndJoin  947540 0:30.013 1.07
                NormalizeWithRegex        1003862 0:29.610 1.00
                
                
                c:\Users\Jon\Test>test 1000 1001
                ============ Normalize ============
                NormalizeWithSplitAndJoin  1156299 0:29.898 21.99
                NormalizeWithRegex        23243802 0:27.335  1.00
                

                这里第一个数字是迭代次数,第二个是所用时间,第三个是比例分数,1.0 是最好的。

                这表明至少在某些情况下(包括这种情况),正则表达式可以胜过拆分/连接解决方​​案,有时甚至有很大的优势。

                但是,如果您更改为“全空白”要求,那么拆分/加入 确实 似乎会胜出。通常情况下,魔鬼在细节中......

                【讨论】:

                • 很好的分析。所以看起来我们在不同程度上都是正确的。我的答案中的代码取自一个更大的函数,该函数能够规范化字符串内以及开头和结尾的所有空格和/或控制字符。
                • 仅使用您指定的空白字符,在我的大多数测试中,正则表达式和 Split/Join 大致相等 - S/J 有一个微小的好处,但代价是正确性和复杂性。由于这些原因,我通常更喜欢正则表达式。不要误会我的意思——我远不是一个正则表达式迷,但我不喜欢在没有真正测试性能的情况下为了性能而编写更复杂的代码。
                • NormalizeWithSplitAndJoin 会产生更多垃圾,很难判断一个真正的问题是否会比基准测试更多的 GC 时间。
                • @IanRingrose 可以创建什么样的垃圾?
                【解决方案13】:

                正则表达式是最简单的方法。如果您以正确的方式编写正则表达式,则不需要多次调用。

                改成这样:

                string s = System.Text.RegularExpressions.Regex.Replace(s, @"\s{2,}", " "); 
                

                【讨论】:

                • @"\s{2,}" 的一个问题是它无法用空格替换单个制表符和其他 Unicode 空格字符。如果您要用空格替换 2 个选项卡,那么您可能应该用空格替换 1 个选项卡。 @"\s+" 会为你做这件事。
                【解决方案14】:

                正如已经指出的,这很容易通过正则表达式完成。我只是补充一点,您可能想要添加一个 .trim() 以消除前导/尾随空格。

                【讨论】:

                  【解决方案15】:

                  虽然现有的答案很好,但我想指出一种行不通的方法

                  public static string DontUseThisToCollapseSpaces(string text)
                  {
                      while (text.IndexOf("  ") != -1)
                      {
                          text = text.Replace("  ", " ");
                      }
                      return text;
                  }
                  

                  这可以永远循环。有人想猜猜为什么吗? (我只是在几年前作为新闻组问题被问到这个问题时才遇到这个问题......实际上有人遇到了这个问题。)

                  【讨论】:

                  • 我想我记得不久前有人问过这个问题。 IndexOf 会忽略 Replace 不会忽略的某些字符。所以双倍空间一直存在,只是从未被删除。
                  • 这是因为 IndexOf 忽略了一些 Unicode 字符,在这种情况下,具体的罪魁祸首是一些亚洲字符 iirc。嗯,根据谷歌的零宽度非连接器。
                  • 我学到了这个艰难的方法:( stackoverflow.com/questions/9260693/…
                  • 我学得很辛苦。特别是两个零宽度非连接器 (\u200C\u200C)。 IndexOf 返回此“双空格”的索引,但 Replace 不会替换它。我认为这是因为对于 IndexOf,您需要指定 StringComparsion (Ordinal) 的行为与 Replace 相同。这样,这两个都不会找到“双空格”。更多关于 StringComparsion docs.microsoft.com/en-us/dotnet/api/…
                  【解决方案16】:
                  Regex regex = new Regex(@"\W+");
                  string outputString = regex.Replace(inputString, " ");
                  

                  【讨论】:

                  • 这会将所有非单词字符替换为空格。所以它也会替换括号和引号之类的东西,这可能不是你想要的。
                  猜你喜欢
                  • 1970-01-01
                  • 2018-02-09
                  • 2013-05-30
                  • 2013-07-03
                  • 2013-11-25
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多