113

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

"Hello     how are   you           doing?"

我想要一个将多个空间变成一个空间的功能。

所以我会得到:

"Hello how are you doing?"

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

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

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

是否已经有内置方法?

4

16 回答 16

206
string cleanedString = System.Text.RegularExpressions.Regex.Replace(dirtyString,@"\s+"," ");
于 2009-08-14T19:57:50.390 回答
53

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

“空格”和“空白”是有区别的。如果您指空格,那么您应该使用" {2,}". 如果您的意思是任何空格,那是另一回事。是否应该将所有空格都转换为空格?空间在开始和结束时应该发生什么?

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

请注意,正确性几乎总是比性能更重要。就您指定的要求而言,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 解决方案,有时甚至有很大的优势。

但是,如果您更改为“全空白”要求,那么拆分/加入似乎确实会赢。就像经常发生的那样,魔鬼在细节中......

于 2009-08-14T21:05:13.123 回答
19

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

将其更改为:

string s = System.Text.RegularExpressions.Regex.Replace(s, @"\s{2,}", " "); 
于 2009-08-14T19:58:37.517 回答
18

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

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

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

于 2009-08-14T20:03:33.247 回答
5

这是我使用的解决方案。没有 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();
于 2014-12-16T10:23:53.973 回答
5

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由Felipe Machado 2015 和 Sunsetquest 修改

Felipe Machado 2015 和 Sunsetquest 修改的 InPlaceCharArraySpaceOnly

SwitchStmtBuild由Felipe Machado 2015 和 Sunsetquest 修改

Felipe Machado 2015 的 SwitchStmtBuild2并由Sunsetquest 修改

David S的 SingleSpacedTrim 2013

Fubo(StringBuilder) by fubo 2014

乔恩·斯基特的 SplitAndJoinOnSpace 2009

2009 年Jon Skeet的 RegExWithCompile

用户 214147 由用户 214147

布兰登的 RegExBrandon

Tim Hoolihan的RegExNoCompile

基准代码在 Github

于 2016-10-05T05:02:48.213 回答
4

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

于 2009-08-14T20:19:08.083 回答
4

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

    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();
    }
于 2010-01-28T17:53:13.237 回答
2

VB.NET

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

C#

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

享受 LINQ 的力量 =D

于 2013-10-14T15:43:00.703 回答
2

使用 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() 非常快而且很难被击败!!

于 2013-12-20T14:50:48.923 回答
1
Regex regex = new Regex(@"\W+");
string outputString = regex.Replace(inputString, " ");
于 2009-08-14T19:59:15.847 回答
0

最小的解决方案:

var regExp=/\s+/g,
newString=oldString.replace(regExp,' ');
于 2009-08-26T10:43:52.257 回答
0

你可以试试这个:

    /// <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();
    }
于 2019-03-29T02:27:44.717 回答
0

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

    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组合,只需从模式中删除反向引用。

于 2019-11-06T21:27:25.910 回答
0
string.Join(" ", s.Split(" ").Where(r => r != ""));
于 2021-03-24T13:41:58.897 回答
-1

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

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 所做的分析。)

于 2009-08-14T20:00:31.320 回答