Skuteczny sposób na usunięcie WSZYSTKICH białych znaków z ciągu?

358

Dzwonię do interfejsu API REST i otrzymuję odpowiedź XML. Zwraca listę nazw obszarów roboczych, a ja piszę szybką IsExistingWorkspace()metodę. Ponieważ wszystkie obszary robocze składają się z ciągłych znaków bez białych spacji, zakładam, że najłatwiejszym sposobem sprawdzenia, czy dany obszar roboczy znajduje się na liście, jest usunięcie wszystkich białych spacji (w tym nowych linii) i zrobienie tego (XML to ciąg otrzymany z sieci żądanie):

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

Wiem, że wielkość liter ma znaczenie i polegam na tym. Potrzebuję tylko sposobu na skuteczne usunięcie wszystkich białych znaków w ciągu. Wiem, że RegEx i LINQ mogą to zrobić, ale jestem otwarty na inne pomysły. Martwię się głównie o prędkość.

Corey Ogburn
źródło
6
Parsowanie XML z regex jest prawie tak samo złe jak parsowanie HTML z regex .
dtb
3
@henk holterman; Zobacz moją odpowiedź poniżej, wyrażenie regularne nie wydaje się być najszybsze we wszystkich przypadkach.
Henk J Meulekamp
Regex wcale nie wydaje się najszybszy. Podsumowałem wyniki z wielu różnych sposobów usuwania białych znaków z łańcucha. Podsumowanie znajduje się w odpowiedzi poniżej - stackoverflow.com/a/37347881/582061
Stian Standahl

Odpowiedzi:

616

To najszybszy sposób, jaki znam, nawet jeśli powiedziałeś, że nie chcesz używać wyrażeń regularnych:

Regex.Replace(XML, @"\s+", "")
Slandau
źródło
1
Mógłbym użyć wyrażenia regularnego, po prostu nie jestem pewien, czy to najszybszy sposób.
Corey Ogburn,
1
Jestem prawie pewien, że tak. Przynajmniej za kulisami musisz sprawdzić każdą postać, a to jest po prostu wyszukiwanie liniowe.
slandau
19
Czy nie powinno tak być Regex.Replace(XML, @"\s+", "")?
Jan-Peter Vos
61
Jeśli planujesz to zrobić więcej niż raz, utwórz i zapisz wystąpienie Regex. Pozwoli to zaoszczędzić koszty związane z budowaniem go za każdym razem, co jest droższe, niż mogłoby się wydawać. private static readonly Regex sWhitespace = new Regex(@"\s+"); public static string ReplaceWhitespace(string input, string replacement) { return sWhitespace.Replace(input, replacement); }
hypehuman
10
Dla nowych użytkowników RegEx i szukających wyjaśnienia, co oznacza to wyrażenie, \soznacza „dopasuj dowolny token białych znaków” i +„dopasuj jeden lub więcej tokenów postępowania”. Również RegExr to strona miło ćwiczyć pisanie wyrażeń regex z, jeśli chcesz eksperymentować.
jrh
181

Mam alternatywny sposób bez wyrażeń regularnych i wydaje się, że działa całkiem dobrze. Jest to kontynuacja odpowiedzi Brandona Moretza:

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

Testowałem to w prostym teście jednostkowym:

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

W przypadku 1 000 000 prób pierwsza opcja (bez wyrażenia regularnego) działa w mniej niż sekundę (700 ms na moim komputerze), a druga zajmuje 3,5 sekundy.

Henk J Meulekamp
źródło
40
.ToCharArray()to nie jest konieczne; możesz użyć .Where()bezpośrednio na sznurku.
ProgramFOX
10
Uwaga: tutaj. Regex działa wolniej ... na małych strunach! Jeśli powiesz, że masz cyfrową wersję tomu o amerykańskim prawie podatkowym (~ milion słów?), Z kilkoma iteracjami, Regex jest zdecydowanie królem! Nie chodzi o to, co jest szybsze, ale o to, co należy wykorzystać w jakich okolicznościach. Udowodniłeś tutaj tylko połowę równania. -1, dopóki nie udowodnisz drugiej połowy testu, aby odpowiedź zapewniła lepszy wgląd w to, kiedy należy użyć.
Piotr Kula,
17
@pumumkin Poprosił o jednorazowe usunięcie spacji. Brak wielokrotnych powtórzeń innego przetwarzania. Nie zamierzam przekształcić tego jednoprzebiegowego usuwania białych znaków w obszerny post na temat przetwarzania tekstu porównawczego.
Henk J Meulekamp,
1
Powiedziałeś, że tym razem wolisz nie używać wyrażenia regularnego, ale nie powiedziałeś dlaczego.
Piotr Kula,
2
@ProgramFOX, w innym pytaniu (nie mogę go łatwo znaleźć) zauważyłem, że przynajmniej w niektórych zapytaniach użycie ToCharArrayjest szybsze niż użycie .Where()bezpośrednio w ciągu. Ma to coś wspólnego z narzutem na IEnumerable<>każdym etapie iteracji, a jego ToCharArraybycie bardzo wydajnym (kopiowanie bloków) i kompilatorem optymalizuje iterację nad tablicami. Dlaczego ta różnica istnieje, nikt nie był w stanie mi wyjaśnić, ale zmierzyć, zanim usuniesz ToCharArray().
Abel,
87

Wypróbuj metodę zamiany ciągu w C #.

XML.Replace(" ", string.Empty);
Mike_K
źródło
28
Nie usuwa kart ani nowych linii. Jeśli teraz wykonuję wiele operacji usuwania, wykonuję wiele przejść w ciągu.
Corey Ogburn,
11
Głosuj za nie usuwaniem wszystkich białych znaków, jak robią to slandau i odpowiedzi Henka.
Matt Sach
@MattSach dlaczego nie usuwa WSZYSTKICH białych znaków?
Zapnologica
4
@Zapnologica Zastępuje jedynie znaki spacji. OP poprosił również o zastąpienie znaku nowej linii (które są znakami „białych znaków”, nawet jeśli nie są znakami spacji).
Matt Sach
75

Moim rozwiązaniem jest użycie Split and Join i jest on zaskakująco szybki, w rzeczywistości najszybszy z najlepszych odpowiedzi tutaj.

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

Czasy dla 10 000 pętli na prostym łańcuchu z białymi znakami, w tym nowe linie i tabulatory

  • split / join = 60 milisekund
  • linq chararray = 94 milisekundy
  • regex = 437 milisekund

Popraw to, zawijając go w metodzie, aby nadać mu znaczenie, a także uczyń ją metodą rozszerzenia, gdy jesteśmy przy niej ...

public static string RemoveWhitespace(this string str) {
    return string.Join("", str.Split(default(string[]), StringSplitOptions.RemoveEmptyEntries));
}
kernowcode
źródło
3
Naprawdę podoba mi się to rozwiązanie, używam podobnego od dni sprzed LINQ. Jestem pod wrażeniem wydajności LINQ i nieco zaskoczony regex. Być może kod nie był tak optymalny, jak mógłby być dla wyrażenia regularnego (na przykład będziesz musiał buforować obiekt wyrażenia regularnego). Ale sedno problemu polega na tym, że „jakość” danych będzie miała duże znaczenie. Może przy długich ciągach wyrażenie regularne przewyższy inne opcje. Będzie to zabawny punkt odniesienia do wykonania ... :-)
Loudenvier,
1
W jaki sposób default (string []) == lista wszystkich białych znaków? Widzę, że działa, ale nie rozumiem, jak?
Jake Drew,
5
@kernowcode Masz na myśli dwuznaczność między 2 przeciążeniami za pomocą string[]i char[]? musisz tylko określić, który chcesz, np string.Join("", str.Split((string[])null, StringSplitOptions.RemoveEmptyEntries));. : . To właśnie robi to Twoje wezwanie defaultw tym przypadku, ponieważ również zwraca null: pomaga kompilatorowi zdecydować, które przeciążenie wybrać. Stąd mój komentarz, ponieważ stwierdzenie w twoim komentarzu „Split potrzebuje prawidłowej tablicy, a null nie zrobi ...” jest fałszywe. Nic wielkiego, po prostu warto o tym wspomnieć, skoro Jake Drew zapytał, jak to działa. +1 za twoją odpowiedź
Frank J
6
Fajny pomysł ... ale zrobiłbym to w następujący sposób:string.Concat("H \ne llo Wor ld".Split())
michaelkrisper
3
Rozwiązanie michaelkrisper jest bardzo czytelne. Zrobiłem test i „split / join” (162 milisekundy) działało lepiej niż „split / concat” (180 milisekund) dla 10 000 iteracji tego samego łańcucha.
kernowcode
45

Opierając się na odpowiedzi Henksa Stworzyłem kilka metod testowych z jego odpowiedzią i kilka dodanych, bardziej zoptymalizowanych metod. Odkryłem, że wyniki różnią się w zależności od wielkości ciągu wejściowego. Dlatego testowałem z dwoma zestawami wyników. W najszybszej metodzie połączone źródło ma jeszcze szybszy sposób. Ale ponieważ jest to określane jako niebezpieczne, pominąłem to.

Wyniki długiego ciągu wejściowego:

  1. InPlaceCharArray: 2021 ms ( odpowiedź Sunsetquest ) - ( oryginalne źródło )
  2. Łańcuch podzielony, a następnie dołącz: 4277ms ( odpowiedź Kernowcode )
  3. Czytnik ciągów: 6082 ms
  4. LINQ przy użyciu natywnego char.IsWhitespace: 7357 ms
  5. LINQ: 7746 ms ( odpowiedź Henka )
  6. ForLoop: 32320 ms
  7. RegexCompiled: 37157 ms
  8. Regex: 42940 ms

Wyniki krótkiego ciągu wejściowego:

  1. InPlaceCharArray: 108 ms ( odpowiedź Sunsetquest ) - ( oryginalne źródło )
  2. Łańcuch podzielony, a następnie dołącz: 294 ms ( odpowiedź Kernowcode )
  3. Czytnik ciągów: 327 ms
  4. ForLoop: 343 ms
  5. LINQ przy użyciu natywnego char.IsWhitespace: 624 ms
  6. LINQ: 645ms ( odpowiedź Henka )
  7. RegexCompiled: 1671 ms
  8. Regex: 2599 ms

Kod :

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 StringSplitThenJoin(this string str)
    {
        return string.Join("", str.Split(default(string[]), StringSplitOptions.RemoveEmptyEntries));
    }

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

Testy :

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

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

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

        Assert.AreEqual(expected, s);
    }
}

Edycja : Testowałem niezłą liner z Kernowcode.

Stian Standahl
źródło
24

Po prostu alternatywa, ponieważ wygląda całkiem fajnie :) - UWAGA: odpowiedź Henks jest najszybsza z nich.

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

Testowanie 1 000 000 pętli na "This is a simple Test"

Ta metoda = 1,74 sekundy
Regex = 2,58 sekundy
new String(Henks) = 0,82

BlueChippy
źródło
1
Dlaczego zostało to zanegowane? Jest całkowicie do przyjęcia, spełnia wymagania, działa szybciej niż opcja RegEx i jest bardzo czytelny?
BlueChippy,
4
ponieważ można go napisać znacznie krócej: nowy ciąg (input.Where (c =>! Char.IsWhiteSpace (c)). ToArray ());
Bas Smit,
7
Może to prawda - ale odpowiedź wciąż pozostaje, jest czytelna, szybsza niż regex i daje pożądany rezultat. Wiele innych odpowiedzi jest PO tej ... dlatego głosowanie nie ma sensu.
BlueChippy,
2
Czy istnieje jednostka dla „0,82”? Czy jest to miara względna (82%)? Czy możesz edytować swoją odpowiedź, aby była bardziej zrozumiała?
Peter Mortensen
20

Znalazłem fajny artykuł na ten temat na CodeProject autorstwa Felipe Machado (z pomocą Richarda Robertsona )

Przetestował dziesięć różnych metod. Ta jest najszybszą niebezpieczną wersją ...

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

I najszybsza bezpieczna wersja ...

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

Istnieją również niezłe niezależne testy porównawcze dotyczące przepełnienia stosu autorstwa Stiana Standahla, które pokazują również, że funkcja Felipe jest około 300% szybsza niż następna najszybsza funkcja.

Sunsetquest
źródło
Próbowałem przetłumaczyć to na C ++, ale utknąłem trochę. Wszelkie pomysły, dlaczego mój port może zawieść? stackoverflow.com/questions/42135922/…
Jon Cage
2
Nie mogę się oprzeć. Przejrzyj sekcję komentarzy w artykule, do którego się odwołujesz. Znajdziesz mnie jako „Oprogramowanie Basketcase”. Pracował nad tym przez chwilę. Zupełnie o tym zapomniałem, kiedy ten problem znów się pojawił. Dzięki za dobre wspomnienia. :)
Richard Robertson
1
A jeśli chcesz usunąć tylko dodatkowe WS? Co z tym modem stackoverflow.com/questions/17770202/… ?
Tom
Najszybszy jest nieco wolniejszy ;-) Łańcuch jako pojemnik robi się lepiej tutaj (w aplikacji 4:15 do 3:55 => 8,5% mniej, ale gdy pozostawia łańcuch 3:30 => 21,4% mniej, a profiller pokazuje około 50% wydanych w Ta metoda). Tak więc w rzeczywistości ciąg znaków na żywo powinien być około 40% szybszy w porównaniu do (powolnej) konwersji tablicy zastosowanej tutaj.
Tom
15

Jeśli potrzebujesz doskonałej wydajności, w tym przypadku powinieneś unikać LINQ i wyrażeń regularnych. Przeprowadziłem testy wydajności i wydaje się, że jeśli chcesz usunąć białe znaki z początku i końca łańcucha, string.Trim () jest twoją ostateczną funkcją.

Jeśli chcesz usunąć wszystkie białe spacje z łańcucha, następująca metoda działa najszybciej ze wszystkich, które zostały tutaj opublikowane:

    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);
    }
JHM
źródło
Byłbym ciekawy poznać szczegóły waszych testów porównawczych - nie dlatego, że jestem sceptyczny, ale jestem ciekawy kosztów ogólnych związanych z Linq. Jak źle było?
Mark Meuer,
Nie wznowiłem wszystkich testów, ale pamiętam bardzo dużo: wszystko, co wiązało się z Linq, było dużo wolniejsze niż wszystko bez niego. Wszystkie sprytne użycie funkcji string / char i konstruktorów nie miało żadnej różnicy procentowej, jeśli użyto Linq.
JHM
11

Regex to przesada; po prostu użyj rozszerzenia na łańcuchu (dzięki Henk). Jest to trywialne i powinno być częścią frameworka. Tak czy inaczej, oto moja implementacja:

public static partial class Extension
{
    public static string RemoveWhiteSpace(this string self)
    {
        return new string(self.Where(c => !Char.IsWhiteSpace(c)).ToArray());
    }
}
Maksood
źródło
jest to w zasadzie niepotrzebna odpowiedź (wyrażenie regularne to przesada, ale jest szybsze niż podane - i zostało już zaakceptowane?)
W1ll1amvl
Jak korzystać z metod rozszerzenia Linq na łańcuchu? Nie mogę się domyślić, za pomocą których brakuje mi innych niżSystem.Linq
GGirard
Ok wygląda na to, że nie jest to dostępne w PCL, IEnumerable <char> jest warunkowy w implementacji Microsoft String ... I używam Profile259, który nie obsługuje tego :)
GGirard
4

Oto prosta liniowa alternatywa dla rozwiązania RegEx. Nie jestem pewien, co jest szybsze; musiałbyś to przetestować.

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();
}
Brandon Moretz
źródło
3

Musiałem zastąpić białe znaki w ciągu spacjami, ale nie duplikować spacji. np. musiałem przekonwertować coś takiego:

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

do

"a b c d e"

Użyłem następującej metody

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();
}
użytkownik1325543
źródło
2

Zakładam, że twoja odpowiedź XML wygląda następująco:

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

Najlepszym sposobem przetwarzania XML jest użycie parsera XML, takiego jak LINQ to XML :

var doc = XDocument.Parse(xml);

var containsFoo = doc.Root
                     .Elements("name")
                     .Any(e => ((string)e).Trim() == "foo");
dtb
źródło
Po sprawdzeniu, czy dany tag <nazwa> ma odpowiednią wartość, gotowe. Czy parsowanie dokumentu nie miałoby narzutu?
Corey Ogburn,
4
Jasne, ma trochę narzutów. Ale ma tę zaletę, że jest poprawna. Rozwiązanie oparte np. Na wyrażeniach regularnych jest znacznie trudniejsze do uzyskania. Jeśli stwierdzisz, że rozwiązanie LINQ to XML jest zbyt wolne, zawsze możesz go zastąpić czymś szybszym. Ale powinieneś unikać polowania na najbardziej wydajną implementację, zanim dowiesz się, że poprawna jest zbyt wolna.
dtb
To będzie działać na serwerach zaplecza mojego pracodawcy. Lekkość jest tym, czego szukam. Nie chcę czegoś, co „po prostu działa”, ale jest optymalne.
Corey Ogburn
4
LINQ to XML jest jednym z najlżejszych sposobów poprawnej pracy z XML w .NET
dtb
1

Oto kolejny wariant:

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

Jak w przypadku większości innych rozwiązań, nie przeprowadziłem wyczerpujących testów porównawczych, ale działa to wystarczająco dobrze dla moich celów.

Fred
źródło
1

Możemy użyć:

    public static string RemoveWhitespace(this string input)
    {
        if (input == null)
            return null;
        return new string(input.ToCharArray()
            .Where(c => !Char.IsWhiteSpace(c))
            .ToArray());
    }
Tarik BENARAB
źródło
Jest to prawie dokładnie to samo, co odpowiedź Henka powyżej. Jedyną różnicą jest to, że sprawdzasz null.
Corey Ogburn,
Tak, sprawdzanie wartości null jest
ważne
1
Może powinien to być komentarz do jego odpowiedzi. Cieszę się, że to wychowałeś. Nie wiedziałem, że metody rozszerzeń można wywoływać na obiektach zerowych.
Corey Ogburn,
0

Znalazłem różne wyniki, które są prawdziwe. Próbuję zastąpić wszystkie białe znaki pojedynczą spacją, a wyrażenie regularne było bardzo wolne.

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

Najbardziej optymalnie działało dla mnie (w 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 );
}

Najpierw wypróbowałem powyższą procedurę, zastępując każdą postać osobno, ale musiałem przejść do robienia podciągów dla sekcji spacji. Przy stosowaniu do łańcucha znaków o wartości 1 200 000:

  • powyższa procedura kończy się w 25 sekund
  • powyższa procedura + wymiana osobnych znaków w 95 sekund
  • regex przerwany po 15 minutach.
hvanbrug
źródło