web-dev-qa-db-fra.com

Moyen efficace de supprimer TOUS les espaces de String?

J'appelle une API REST et je reçois une réponse XML. Il renvoie une liste de noms d'espaces de travail et j'écris une méthode rapide IsExistingWorkspace(). Étant donné que tous les espaces de travail sont constitués de caractères contigus sans espaces, je suppose que la meilleure façon de savoir si un espace de travail particulier figure dans la liste consiste à supprimer tous les espaces (y compris les nouvelles lignes). Pour ce faire, XML est la chaîne reçue du Web. demande):

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

Je sais que c'est la casse, et je m'appuie sur cela. J'ai juste besoin d'un moyen de supprimer efficacement tous les espaces d'une chaîne. Je sais que RegEx et LINQ peuvent le faire, mais je suis ouvert à d’autres idées. Je suis surtout préoccupé par la vitesse.

315
Corey Ogburn

C'est le moyen le plus rapide que je connaisse, même si vous avez dit que vous ne vouliez pas utiliser d'expressions régulières:

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

J'ai un moyen alternatif sans expression rationnelle, et cela semble assez performant. C'est une continuation de la réponse de Brandon Moretz:

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

Je l'ai testé dans un simple test unitaire:

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

Pour 1 000 000 de tentatives, la première option (sans expression rationnelle) s'exécute en moins d'une seconde (700 ms sur ma machine), et la seconde prend 3,5 secondes.

165
Henk J Meulekamp

Essayez la méthode de remplacement de la chaîne en C #.

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

Ma solution consiste à utiliser Split and Join et elle est étonnamment rapide, en fait la réponse la plus rapide parmi les meilleures réponses ici.

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

Timings pour 10 000 boucles sur une chaîne simple avec des espaces, y compris de nouvelles lignes et de nouveaux onglets

  • split/join = 60 milliseconds
  • linq chararray = 94 millisecondes
  • regex = 437 millisecondes

Améliorez cela en l’enveloppant dans une méthode pour lui donner un sens, et faites-en aussi une méthode d’extension pendant que nous y sommes ...

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

Construire sur réponse Henks J'ai créé des méthodes de test avec sa réponse et des méthodes ajoutées, plus optimisées. J'ai trouvé que les résultats diffèrent en fonction de la taille de la chaîne d'entrée. Par conséquent, j'ai testé avec deux ensembles de résultats. Dans la méthode la plus rapide, la source liée a un moyen encore plus rapide. Mais, comme il est qualifié de dangereux, j’ai laissé cela de côté.

Résultats de longue chaîne d'entrée:

  1. InPlaceCharArray: 2021 ms ( réponse de Sunsetquest ) --- (Source d'origine )
  2. Chaîne scindée puis jointe: 4277ms ( réponse de Kernowcode )
  3. Lecteur de chaîne: 6082 ms
  4. LINQ utilisant le caractère natif.IsWhitespace: 7357 ms
  5. LINQ: 7746 ms ( réponse de Henk )
  6. ForLoop: 32320 ms
  7. RegexCompiled: 37157 ms
  8. Regex: 42940 ms

Résultats de la chaîne de saisie courte:

  1. InPlaceCharArray: 108 ms ( réponse de Sunsetquest ) --- (Source d'origine )
  2. Fractionnement de la chaîne puis jointure: 294 ms ( réponse de Kernowcode )
  3. Lecteur de chaîne: 327 ms
  4. ForLoop: 343 ms
  5. LINQ utilisant le caractère natif.IsWhitespace: 624 ms
  6. LINQ: 645ms ( réponse de Henk )
  7. RegexCompiled: 1671 ms
  8. Regex: 2599 ms

Code :

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

Tests :

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

Edit : a testé un liner Nice one de Kernowcode.

35
Stian Standahl

Juste une alternative parce que ça a l'air plutôt sympa :) - NOTE: Réponse de Henks est le plus rapide de ceux-ci.

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

Test de 1 000 000 de boucles sur "This is a simple Test"

Cette méthode = 1,74 seconde
Regex = 2,58 secondes
new String (Henks) = 0,82

23
BlueChippy

J'ai trouvé ne belle écriture à ce sujet sur CodeProject de Felipe Machado (avec l'aide de Richard Robertson )

Il a testé dix méthodes différentes. Celui-ci est la plus rapide version non sécurisée ...

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

Et la version la plus rapide ...

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

Il y a aussi quelques Nice benchmarks indépendants sur Stack Overflow de Stian Standahl qui montrent également comment la fonction de Felipe est environ 300% plus rapide que la suivante.

17
Sunsetquest

Si vous avez besoin de performances exceptionnelles, évitez dans ce cas les expressions régulières et LINQ. J'ai effectué des analyses comparatives des performances et il semble que si vous souhaitez supprimer les espaces en début et en fin de chaîne, string.Trim () est votre fonction ultime.

Si vous devez supprimer tous les espaces d'une chaîne, la méthode suivante fonctionne le plus rapidement de tout ce qui a été posté ici:

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

Regex est exagéré; il suffit d'utiliser l'extension sur chaîne (merci Henk). Ceci est trivial et aurait dû faire partie du cadre. Quoi qu'il en soit, voici ma mise en œuvre:

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

Voici une alternative linéaire simple à la solution RegEx. Je ne sais pas ce qui est le plus rapide. vous auriez à le comparer.

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

J'avais besoin de remplacer les espaces dans une chaîne par des espaces, mais pas de les dupliquer. Par exemple, je devais convertir quelque chose comme ce qui suit:

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

à

"a b c d e"

J'ai utilisé la méthode suivante

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

Je suppose que votre réponse XML ressemble à ceci:

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

La meilleure façon de traiter XML consiste à utiliser un analyseur XML, tel que LINQ to XML:

var doc = XDocument.Parse(xml);

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

Voici encore une autre variante:

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

Comme pour la plupart des autres solutions, je n'ai pas effectué de tests comparatifs exhaustifs, mais cela fonctionne assez bien pour mes besoins.

1
Fred

On peut utiliser:

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

J'ai trouvé différents résultats pour être vrai. J'essaie de remplacer tous les espaces blancs par un seul espace et la regex était extrêmement lente.

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

Ce qui a fonctionné le plus efficacement pour moi (en C++ cli) a été:

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

J'ai d'abord essayé la routine ci-dessus en remplaçant chaque caractère séparément, mais j'ai dû passer à la sous-chaîne pour les sections sans espace. Lors de l'application à une chaîne de 1 200 000 caractères:

  • la routine ci-dessus se fait en 25 secondes
  • la routine ci-dessus + remplacement de caractère séparé en 95 secondes
  • la regex a avorté après 15 minutes.
0
hvanbrug