Эффективный способ удалить все пробелы из строки?


Я вызываю REST API и получаю ответ XML обратно. Он возвращает список имен рабочего пространства, и я пишу быстрый IsExistingWorkspace() метод. Поскольку все рабочие пространства состоят из смежных символов без пробелов, я предполагаю, что самый простой способ узнать, находится ли конкретное рабочее пространство в списке, - удалить все пробелы (включая новые строки) и сделать это (XML-это строка, полученная из веб-запроса):

XML.Contains("<name>" + workspaceName + "</name>");

Я знаю, что это чувствительно к регистру, и я полагаюсь на этом. Мне просто нужен способ эффективно удалить все пробелы в строке. Я знаю, что RegEx и LINQ могут это сделать, но я открыт для других идей. Я в основном просто беспокоюсь о скорости.

16 278

16 ответов:

Это самый быстрый способ я знаю, хотя вы сказали, что вы не хотите использовать регулярные выражения:

Regex.Replace(XML, @"\s+", "")

у меня есть альтернативный способ без регулярных выражений, и он, кажется, работает довольно хорошо. Это продолжение на ответ Брэндона Морец:

 public static string RemoveWhitespace(this string input)
 {
    return new string(input.ToCharArray()
        .Where(c => !Char.IsWhiteSpace(c))
        .ToArray());
 }

Я проверил его в простом модульном тесте:

[Test]
[TestCase("123 123 1adc \n 222", "1231231adc222")]
public void RemoveWhiteSpace1(string input, string expected)
{
    string s = null;
    for (int i = 0; i < 1000000; i++)
    {
        s = input.RemoveWhitespace();
    }
    Assert.AreEqual(expected, s);
}

[Test]
[TestCase("123 123 1adc \n 222", "1231231adc222")]
public void RemoveWhiteSpace2(string input, string expected)
{
    string s = null;
    for (int i = 0; i < 1000000; i++)
    {
        s = Regex.Replace(input, @"\s+", "");
    }
    Assert.AreEqual(expected, s);
}

для 1 000 000 попыток первый вариант (без регулярного выражения) выполняется менее чем за секунду (700 мс на моей машине), а второй занимает 3,5 секунды.

попробуйте заменить метод строки в C#.

XML.Replace(" ", string.Empty);

мое решение-использовать Split и Join, и это удивительно быстро, на самом деле самый быстрый из лучших ответов здесь.

str = string.Join("", str.Split(default(string[]), StringSplitOptions.RemoveEmptyEntries));

тайминги для цикла 10,000 на простой строке с пробелами inc новые строки и вкладки

  • split / join = 60 миллисекунд
  • linq chararray = 94 миллисекунды
  • регулярное выражение = 437 мсек

улучшите это, обернув его в метод, чтобы придать ему смысл, а также сделать его методом расширения пока мы этим занимаемся ...

public static string RemoveWhitespace(this string str) {
    return string.Join("", str.Split(default(string[]), StringSplitOptions.RemoveEmptyEntries));
}

дом на Henks ответ Я создал некоторые методы тестирования с его ответом и некоторые добавленные, более оптимизированные методы. Я обнаружил, что результаты отличаются в зависимости от размера входной строки. Поэтому я тестировал с двумя результирующими наборами. В самом быстром методе связанный источник имеет еще более быстрый способ. Но, поскольку он характеризуется как небезопасный, я оставил это.

длинные результаты ввода строки:

  1. InPlaceCharArray: 2021 МС ( ответ Sunsetquest) - (первоисточник)
  2. читатель строки: 6082 МС
  3. LINQ с использованием собственного символа.IsWhitespace: 7357 ms
  4. LINQ: 7746 ms ( ответ Хенка)
  5. ForLoop: 32320 ms
  6. RegexCompiled: 37157 ms
  7. регулярное выражение: 42940 МС

короткие результаты ввода строки:

  1. InPlaceCharArray: 108 МС ( ответ Sunsetquest) - (первоисточник)
  2. читатель строки: 327 МС
  3. ForLoop: 343 МС
  4. LINQ с использованием собственного символа.IsWhitespace: 624 ms
  5. LINQ: 645ms ( ответ Хенка)
  6. RegexCompiled: 1671 ms
  7. регулярное выражение: 2599 МС

код:

public class RemoveWhitespace
{
    public static string RemoveStringReader(string input)
    {
        var s = new StringBuilder(input.Length); // (input.Length);
        using (var reader = new StringReader(input))
        {
            int i = 0;
            char c;
            for (; i < input.Length; i++)
            {
                c = (char)reader.Read();
                if (!char.IsWhiteSpace(c))
                {
                    s.Append(c);
                }
            }
        }

        return s.ToString();
    }

    public static string RemoveLinqNativeCharIsWhitespace(string input)
    {
        return new string(input.ToCharArray()
            .Where(c => !char.IsWhiteSpace(c))
            .ToArray());
    }

    public static string RemoveLinq(string input)
    {
        return new string(input.ToCharArray()
            .Where(c => !Char.IsWhiteSpace(c))
            .ToArray());
    }

    public static string RemoveRegex(string input)
    {
        return Regex.Replace(input, @"\s+", "");
    }

    private static Regex compiled = new Regex(@"\s+", RegexOptions.Compiled);
    public static string RemoveRegexCompiled(string input)
    {
        return compiled.Replace(input, "");
    }

    public static string RemoveForLoop(string input)
    {
        for (int i = input.Length - 1; i >= 0; i--)
        {
            if (char.IsWhiteSpace(input[i]))
            {
                input = input.Remove(i, 1);
            }
        }
        return input;
    }

    public static string RemoveInPlaceCharArray(string input)
    {
        var len = input.Length;
        var src = input.ToCharArray();
        int dstIdx = 0;
        for (int i = 0; i < len; i++)
        {
            var ch = src[i];
            switch (ch)
            {
                case '\u0020':
                case '\u00A0':
                case '\u1680':
                case '\u2000':
                case '\u2001':
                case '\u2002':
                case '\u2003':
                case '\u2004':
                case '\u2005':
                case '\u2006':
                case '\u2007':
                case '\u2008':
                case '\u2009':
                case '\u200A':
                case '\u202F':
                case '\u205F':
                case '\u3000':
                case '\u2028':
                case '\u2029':
                case '\u0009':
                case '\u000A':
                case '\u000B':
                case '\u000C':
                case '\u000D':
                case '\u0085':
                    continue;
                default:
                    src[dstIdx++] = ch;
                    break;
            }
        }
        return new string(src, 0, dstIdx);
    }
}

тесты:

[TestFixture]
public class Test
{
    // Short input
    //private const string input = "123 123 \t 1adc \n 222";
    //private const string expected = "1231231adc222";

    // Long input
    private const string input = "123 123 \t 1adc \n 222123 123 \t 1adc \n 222123 123 \t 1adc \n 222123 123 \t 1adc \n 222123 123 \t 1adc \n 222123 123 \t 1adc \n 222123 123 \t 1adc \n 222123 123 \t 1adc \n 222123 123 \t 1adc \n 222123 123 \t 1adc \n 222123 123 \t 1adc \n 222123 123 \t 1adc \n 222123 123 \t 1adc \n 222123 123 \t 1adc \n 222123 123 \t 1adc \n 222123 123 \t 1adc \n 222123 123 \t 1adc \n 222123 123 \t 1adc \n 222123 123 \t 1adc \n 222123 123 \t 1adc \n 222123 123 \t 1adc \n 222123 123 \t 1adc \n 222";
    private const string expected = "1231231adc2221231231adc2221231231adc2221231231adc2221231231adc2221231231adc2221231231adc2221231231adc2221231231adc2221231231adc2221231231adc2221231231adc2221231231adc2221231231adc2221231231adc2221231231adc2221231231adc2221231231adc2221231231adc2221231231adc2221231231adc2221231231adc222";

    private const int iterations = 1000000;

    [Test]
    public void RemoveInPlaceCharArray()
    {
        string s = null;
        var stopwatch = Stopwatch.StartNew();
        for (int i = 0; i < iterations; i++)
        {
            s = RemoveWhitespace.RemoveInPlaceCharArray(input);
        }

        stopwatch.Stop();
        Console.WriteLine("InPlaceCharArray: " + stopwatch.ElapsedMilliseconds + " ms");
        Assert.AreEqual(expected, s);
    }

    [Test]
    public void RemoveStringReader()
    {
        string s = null;
        var stopwatch = Stopwatch.StartNew();
        for (int i = 0; i < iterations; i++)
        {
            s = RemoveWhitespace.RemoveStringReader(input);
        }

        stopwatch.Stop();
        Console.WriteLine("String reader: " + stopwatch.ElapsedMilliseconds + " ms");
        Assert.AreEqual(expected, s);
    }

    [Test]
    public void RemoveLinqNativeCharIsWhitespace()
    {
        string s = null;
        var stopwatch = Stopwatch.StartNew();
        for (int i = 0; i < iterations; i++)
        {
            s = RemoveWhitespace.RemoveLinqNativeCharIsWhitespace(input);
        }

        stopwatch.Stop();
        Console.WriteLine("LINQ using native char.IsWhitespace: " + stopwatch.ElapsedMilliseconds + " ms");
        Assert.AreEqual(expected, s);
    }

    [Test]
    public void RemoveLinq()
    {
        string s = null;
        var stopwatch = Stopwatch.StartNew();
        for (int i = 0; i < iterations; i++)
        {
            s = RemoveWhitespace.RemoveLinq(input);
        }

        stopwatch.Stop();
        Console.WriteLine("LINQ: " + stopwatch.ElapsedMilliseconds + " ms");
        Assert.AreEqual(expected, s);
    }

    [Test]
    public void RemoveRegex()
    {
        string s = null;
        var stopwatch = Stopwatch.StartNew();
        for (int i = 0; i < iterations; i++)
        {
            s = RemoveWhitespace.RemoveRegex(input);
        }

        stopwatch.Stop();
        Console.WriteLine("Regex: " + stopwatch.ElapsedMilliseconds + " ms");

        Assert.AreEqual(expected, s);
    }

    [Test]
    public void RemoveRegexCompiled()
    {
        string s = null;
        var stopwatch = Stopwatch.StartNew();
        for (int i = 0; i < iterations; i++)
        {
            s = RemoveWhitespace.RemoveRegexCompiled(input);
        }

        stopwatch.Stop();
        Console.WriteLine("RegexCompiled: " + stopwatch.ElapsedMilliseconds + " ms");

        Assert.AreEqual(expected, s);
    }

    [Test]
    public void RemoveForLoop()
    {
        string s = null;
        var stopwatch = Stopwatch.StartNew();
        for (int i = 0; i < iterations; i++)
        {
            s = RemoveWhitespace.RemoveForLoop(input);
        }

        stopwatch.Stop();
        Console.WriteLine("ForLoop: " + stopwatch.ElapsedMilliseconds + " ms");

        Assert.AreEqual(expected, s);
    }
}

просто альтернатива, потому что это выглядит довольно мило :) - Примечание: Henks ответ - самый быстрый из них.

input.ToCharArray()
 .Where(c => !Char.IsWhiteSpace(c))
 .Select(c => c.ToString())
 .Aggregate((a, b) => a + b);

тестирование 1,000,000 циклов на "This is a simple Test"

этот метод = 1.74 секунд
Регулярное выражение = 2.58 секунд
new String (Henks) = 0.82

Если вам нужна Превосходная производительность, вы должны избегать LINQ и регулярных выражений в этом случае. Я сделал некоторые бенчмаркинг производительности, и кажется, что если вы хотите, чтобы очистить пустое пространство от начала и конца строки, строка.Trim () - это ваша конечная функция.

Если вам нужно вырезать все пробелы из строки, следующий метод работает быстрее всего, что было опубликовано здесь:

    public static string RemoveWhitespace(this string input)
    {
        int j = 0, inputlen = input.Length;
        char[] newarr = new char[inputlen];

        for (int i = 0; i < inputlen; ++i)
        {
            char tmp = input[i];

            if (!char.IsWhiteSpace(tmp))
            {
                newarr[j] = tmp;
                ++j;
            }
        }
        return new String(newarr, 0, j);
    }

нашел хорошая запись об этом на CodeProject Фелипе Мачадо (с помощью Ричард Робертсон)

он испытал десять различных методов. Это один из самых быстрых небезопасных версия...

public static unsafe string TrimAllWithStringInplace(string str) {
    fixed (char* pfixed = str) {
        char* dst = pfixed;
        for (char* p = pfixed; *p != 0; p++)

            switch (*p) {

                case '\u0020': case '\u00A0': case '\u1680': case '\u2000': case '\u2001':

                case '\u2002': case '\u2003': case '\u2004': case '\u2005': case '\u2006':

                case '\u2007': case '\u2008': case '\u2009': case '\u200A': case '\u202F':

                case '\u205F': case '\u3000': case '\u2028': case '\u2029': case '\u0009':

                case '\u000A': case '\u000B': case '\u000C': case '\u000D': case '\u0085':
                    continue;

                default:
                    *dst++ = *p;
                    break;
    }

    return new string(pfixed, 0, (int)(dst - pfixed));
}

и самый быстрый безопасное версия...

public static string TrimAllWithInplaceCharArray(string str) {

    var len = str.Length;
    var src = str.ToCharArray();
    int dstIdx = 0;

    for (int i = 0; i < len; i++) {
        var ch = src[i];

        switch (ch) {

            case '\u0020': case '\u00A0': case '\u1680': case '\u2000': case '\u2001':

            case '\u2002': case '\u2003': case '\u2004': case '\u2005': case '\u2006':

            case '\u2007': case '\u2008': case '\u2009': case '\u200A': case '\u202F':

            case '\u205F': case '\u3000': case '\u2028': case '\u2029': case '\u0009':

            case '\u000A': case '\u000B': case '\u000C': case '\u000D': case '\u0085':
                continue;

            default:
                src[dstIdx++] = ch;
                break;
        }
    }
    return new string(src, 0, dstIdx);
}

есть также некоторые хорошие независимые показатели на переполнение стека по Stian Standahl, что также покажите, как функция Фелипе примерно на 300% быстрее, чем следующая самая быстрая функция.

регулярное выражение является излишним; просто используйте расширение на строку (спасибо Henk). Это тривиально и должно было быть частью системы. Во всяком случае, вот моя реализация:

public static partial class Extension
{
    public static string RemoveWhiteSpace(this string self)
    {
        return new string(self.Where(c => !Char.IsWhiteSpace(c)).ToArray());
    }
}

мне нужно было заменить пробел в строке пробелами, но не дублировать пробелы. например, мне нужно было преобразовать что-то вроде следующего:

"a b   c\r\n d\t\t\t e"

до

"a b c d e"

я использовал следующий метод

private static string RemoveWhiteSpace(string value)
{
    if (value == null) { return null; }
    var sb = new StringBuilder();

    var lastCharWs = false;
    foreach (var c in value)
    {
        if (char.IsWhiteSpace(c))
        {
            if (lastCharWs) { continue; }
            sb.Append(' ');
            lastCharWs = true;
        }
        else
        {
            sb.Append(c);
            lastCharWs = false;
        }
    }
    return sb.ToString();
}

вот простая линейная альтернатива решению регулярных выражений. Я не уверен, что быстрее; вам придется сравнивать его.

static string RemoveWhitespace(string input)
{
    StringBuilder output = new StringBuilder(input.Length);

    for (int index = 0; index < input.Length; index++)
    {
        if (!Char.IsWhiteSpace(input, index))
        {
            output.Append(input[index]);
        }
    }
    return output.ToString();
}

Я предполагаю, что ваш XML-ответ выглядит так:

var xml = @"<names>
                <name>
                    foo
                </name>
                <name>
                    bar
                </name>
            </names>";

лучший способ обработки XML-это использовать синтаксический анализатор XML, например LINQ to XML:

var doc = XDocument.Parse(xml);

var containsFoo = doc.Root
                     .Elements("name")
                     .Any(e => ((string)e).Trim() == "foo");

вот еще один вариант:

public static string RemoveAllWhitespace(string aString)
{
  return String.Join(String.Empty, aString.Where(aChar => aChar !Char.IsWhiteSpace(aChar)));
}

Как и в большинстве других решений, я не выполнил исчерпывающие тестовые тесты, но это работает достаточно хорошо для моих целей.

Я нашел разные результаты, чтобы быть правдой. Я пытаюсь заменить все пробелы одним пробелом, и регулярное выражение было очень медленным.

return( Regex::Replace( text, L"\s+", L" " ) );

что работало наиболее оптимально для меня (в C++ cli) было:

String^ ReduceWhitespace( String^ text )
{
  String^ newText;
  bool    inWhitespace = false;
  Int32   posStart = 0;
  Int32   pos      = 0;
  for( pos = 0; pos < text->Length; ++pos )
  {
    wchar_t cc = text[pos];
    if( Char::IsWhiteSpace( cc ) )
    {
      if( !inWhitespace )
      {
        if( pos > posStart ) newText += text->Substring( posStart, pos - posStart );
        inWhitespace = true;
        newText += L' ';
      }
      posStart = pos + 1;
    }
    else
    {
      if( inWhitespace )
      {
        inWhitespace = false;
        posStart = pos;
      }
    }
  }

  if( pos > posStart ) newText += text->Substring( posStart, pos - posStart );

  return( newText );
}

сначала я попробовал эту процедуру, заменив каждый символ отдельно, но мне пришлось переключиться на выполнение подстрок для разделов без пробела. При применении к 1 200 000 символьной строке:

  • вышеуказанная процедура делает это за 25 секунд
  • вышеуказанная процедура + отдельная замена символов за 95 секунд
  • регулярное выражение прервано через 15 минут.

мы можем использовать:

    public static string RemoveWhitespace(this string input)
    {
        if (input == null)
            return null;
        return new string(input.ToCharArray()
            .Where(c => !Char.IsWhiteSpace(c))
            .ToArray());
    }

мы можем использовать систему.Linq и мы можем сделать это в одной строке:

string text = "My text with white spaces...";
text = new string(text.ToList().Where(c => c != ' ').ToArray());