How to replace multiple white spaces with one white space

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

"Hello     how are   you           doing?"

我想要一个函数,把多个空间变成一个空间。

所以我会得到:

"Hello how are you doing?"

我知道我可以使用 regex 或调用

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

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

Is there already a built in method for this?

116620 次浏览
string cleanedString = System.Text.RegularExpressions.Regex.Replace(dirtyString,@"\s+"," ");

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

改成这样:

string s = System.Text.RegularExpressions.Regex.Replace(s, @"\s{2,}", " ");
Regex regex = new Regex(@"\W+");
string outputString = regex.Replace(inputString, " ");

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

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 完成的 分析。)

虽然现有的答案是好的,我想指出一种方法 没有的工作:

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

这可能会永远循环下去。有人想知道为什么吗?(直到几年前在新闻组被问到这个问题时,我才意识到这个问题... ... 实际上有人碰到了这个问题。)

正如已经指出的,这很容易通过正则表达式完成。我只想说,你可能需要加一个。去掉前/后的空格。

这个问题并不像其他海报所描述的那么简单(我最初也是这么认为的)——因为这个问题并不像它需要的那样精确。

“空格”和“空白”是有区别的。如果 只有是空格,那么应该使用 " {2,}"的正则表达式。如果你指的是 任何空格,那就是另一回事了。所有空格应该转换为空格吗?在开始和结束的时候,空间会发生什么变化?

对于下面的基准测试,我假设您只关心空格,并且不希望对单个空格进行任何操作,即使在开始和结束时也是如此。

Note that correctness is almost always more important than performance. The fact that the Split/Join solution removes any leading/trailing whitespace (even just single spaces) is incorrect as far as your specified requirements (which may be incomplete, of course).

基准测试使用 迷你长椅

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是最好的。

这表明,至少在某些情况下(包括本例) ,正则表达式 can的性能优于 Split/Join 解决方案,有时优势非常明显。

但是,如果您更改为“所有空格”要求,那么 Split/Join是的似乎胜出。通常情况下,细节决定成败。

最小的解决方案:

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

我在分享我使用的东西,因为看起来我想到了一些不同的东西。我已经用了一段时间了,它对我来说已经足够快了。我不知道这和其他人有什么区别。我在分隔文件编写器中使用它,并通过它一次运行一个字段的大型数据表。

    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();
}

VB.NET

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

C#

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

Enjoy the power of LINQ =D

通过使用 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 所花费的时间。追加()并且要做得更好将需要使用不安全的代码。

So Regex.Replace() is very fast and hard to beat!!

这是我使用的解决方案。没有正则表达式和字符串。拆分。

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();

一个快速的额外空格去除器由 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运行,只有短字符串测试

仅由 Felipe Machado2015修改,由 Sunsetquest 修改

InPlaceCharraySpaceOnly 由 Felipe Machado2015修改,Sunsetquest 修改

Felipe Machado2015修改并由 Sunsetquest 修改的 SwitchStmtBuild

SwitchStmtBuild2由 Felipe Machado2015修改,由 Sunsetquest 修改

SingleSpacedTrim by David S 2013

Fubo (StringBuilder) by Fubo2014

2009年由 Jon Skeet提供的 SplitAndJoinOnSpace

2009年 Jon Skeet的 RegExWithCompile

用户214147 by User214147

摄影: Brandon

Tim Hoolihan编译

基准代码在 Github 上

你可以试试这个:

    /// <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();
}

替换组提供了更简单的方法来解决用 一样单个字符替换多个 留白字符的问题:

    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

string.Join(" ", s.Split(" ").Where(r => r != ""));