Comment remplacer plusieurs espaces blancs par un seul espace blanc


108

Disons que j'ai une chaîne telle que:

"Hello     how are   you           doing?"

Je voudrais une fonction qui transforme plusieurs espaces en un seul espace.

Alors j'aurais:

"Hello how are you doing?"

Je sais que je pourrais utiliser l'expression régulière ou appeler

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

Mais je devrais l'appeler plusieurs fois pour m'assurer que tous les espaces séquentiels sont remplacés par un seul.

Existe-t-il déjà une méthode intégrée pour cela?


Pourriez-vous clarifier: avez-vous affaire uniquement à des espaces ou à «tous» les espaces?
Jon Skeet

Et voulez-vous que les espaces non-espace soient convertis en espaces?
Jon Skeet

Je voulais juste dire que tous les espaces blancs de la série devraient être au plus 1
Matt

1
Copie

2 choses à considérer: 1. char.IsWhiteSpace inclut le retour chariot, le saut de ligne, etc. 2. 'whitespace' est probablement testé plus précisément avec Char.GetUnicodeCategory (ch) = Globalization.UnicodeCategory.SpaceSeparator
smirkingman

Réponses:


196
string cleanedString = System.Text.RegularExpressions.Regex.Replace(dirtyString,@"\s+"," ");

40
imo, éviter les regex si vous
êtes à l'

8
Si votre application n'a pas de temps critique, elle peut se permettre la 1 microseconde de surcharge de traitement.
Daniel

16
Notez que «\ s» remplace non seulement les espaces blancs, mais également les caractères de nouvelle ligne.
Bart Kiers

12
bonne prise, si vous voulez juste des espaces, changez le motif en "[] ​​+"
Tim Hoolihan

9
Ne devriez-vous pas utiliser «{2,}» au lieu de «+» pour éviter de remplacer des espaces blancs simples?
angularsen

52

Cette question n'est pas aussi simple que d'autres affiches l'ont prétendu (et comme je le croyais au départ) - parce que la question n'est pas aussi précise qu'elle le devrait.

Il y a une différence entre «espace» et «espace blanc». Si vous ne parlez que d' espaces, vous devez utiliser une expression régulière de " {2,}". Si vous voulez dire des espaces, c'est une autre question. Tous les espaces doivent- ils être convertis en espaces? Que devrait-il arriver à l'espace au début et à la fin?

Pour le benchmark ci-dessous, j'ai supposé que vous ne vous souciez que des espaces et que vous ne voulez rien faire sur des espaces uniques, même au début et à la fin.

Notez que l'exactitude est presque toujours plus importante que les performances. Le fait que la solution Split / Join supprime tout espace blanc de début / fin (même des espaces simples) est incorrect en ce qui concerne vos exigences spécifiées (qui peuvent être incomplètes, bien sûr).

Le benchmark utilise 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);
    }
}

Quelques essais:

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

Ici, le premier nombre est le nombre d'itérations, le second est le temps nécessaire et le troisième est un score mis à l'échelle, 1,0 étant le meilleur.

Cela montre que dans au moins certains cas (y compris celui-ci) une expression régulière peut surpasser la solution Split / Join, parfois avec une marge très significative.

Toutefois, si vous passez à une exigence « tous les espaces », puis de Split / Join ne semble gagner. Comme c'est souvent le cas, le diable est dans le détail ...


1
Excellente analyse. Il semble donc que nous avions tous deux raison à des degrés divers. Le code de ma réponse a été tiré d'une fonction plus large qui a la capacité de normaliser tous les espaces et / ou de contrôler les caractères à partir d'une chaîne et du début et de la fin.
Scott Dorman

1
Avec juste les caractères d'espacement que vous avez spécifiés, dans la plupart de mes tests, l'expression régulière et Split / Join étaient à peu près égaux - S / J avait un minuscule avantage, au prix de l'exactitude et de la complexité. Pour ces raisons, je préfère normalement le regex. Ne vous méprenez pas - je suis loin d'être un fan des regex, mais je n'aime pas écrire du code plus complexe pour des raisons de performances sans vraiment tester les performances au préalable.
Jon Skeet

NormalizeWithSplitAndJoin créera beaucoup plus de déchets, il est difficile de dire si un problème réel sera touché plus de temps GC que le banchmark.
Ian Ringrose

@IanRingrose Quelle sorte de déchets peut être créé?
Dronz

18

Un expressoin régulier serait le moyen le plus simple. Si vous écrivez le regex correctement, vous n'aurez pas besoin de plusieurs appels.

Changez-le en ceci:

string s = System.Text.RegularExpressions.Regex.Replace(s, @"\s{2,}", " "); 

Mon seul problème @"\s{2,}"est qu'il ne parvient pas à remplacer les onglets simples et les autres caractères d'espace Unicode par un espace. Si vous allez remplacer 2 onglets par un espace, vous devriez probablement remplacer 1 onglet par un espace. @"\s+"fera cela pour vous.
David Specht

17

Bien que les réponses existantes soient correctes, j'aimerais souligner une approche qui ne fonctionne pas :

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

Cela peut boucler pour toujours. Quelqu'un veut-il deviner pourquoi? (Je n'ai rencontré cela que lorsque cela a été posé comme question de groupe de discussion il y a quelques années ... quelqu'un l'a en fait rencontré comme un problème.)


Je pense que je me souviens que cette question a été posée il y a quelque temps sur SO. IndexOf ignore certains caractères que Replace ne fait pas. Le double espace était donc toujours là, jamais supprimé.
Brandon

19
C'est parce que IndexOf ignore certains caractères Unicode, le coupable spécifique dans ce cas étant un caractère asiatique iirc. Hmm, non-menuisier de largeur zéro selon Google.
ahawker


J'ai appris à la dure. Spécialement avec deux non jointeurs de largeur zéro (\ u200C \ u200C). IndexOf renvoie l'index de ce "double espace", mais Replace ne le remplace pas. Je pense que c'est parce que pour IndexOf, vous devez spécifier StringComparsion (Ordinal) pour se comporter de la même manière que Replace. De cette façon, aucun de ces deux ne localisera les «espaces doubles». En savoir plus sur StringComparsion docs.microsoft.com/en-us/dotnet/api/…
Martin Brabec

4

Comme déjà souligné, cela se fait facilement par une expression régulière. J'ajouterai simplement que vous voudrez peut-être ajouter un .trim () à cela pour vous débarrasser des espaces de début / de fin.


4

Voici la solution avec laquelle je travaille. Sans RegEx et 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();
}

afin que vous puissiez:

string cleanedString = dirtyString.TrimWhiteSpace();

4

Un dissolvant rapide des espaces blancs par Felipe Machado. (Modifié par RW pour la suppression de plusieurs espaces)

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

Les repères ...

|                           | 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     |

Notes de référence: mode de sortie, aucun débogueur connecté, processeur i7, moyenne de 4 exécutions, seules les chaînes courtes testées

SwitchStmtBuildSpaceOnly par Felipe Machado 2015 et modifié par Sunsetquest

InPlaceCharArraySpaceOnly par Felipe Machado 2015 et modifié par Sunsetquest

SwitchStmtBuild par Felipe Machado 2015 et modifié par Sunsetquest

SwitchStmtBuild2 par Felipe Machado 2015 et modifié par Sunsetquest

SingleSpacedTrim par David S 2013

Fubo (StringBuilder) par fubo 2014

SplitAndJoinOnSpace par Jon Skeet 2009

RegExWithCompile par Jon Skeet 2009

User214147 par user214147

RegExBrandon par Brandon

RegExNoCompile par Tim Hoolihan

Le code de référence est sur Github


1
Ravi de voir mon article référencé ici! (Je suis Felipe Machado) Je suis sur le point de le mettre à jour en utilisant un outil de référence approprié appelé BenchmarkDotNet! Je vais essayer de configurer les exécutions dans tous les temps d'exécution (maintenant que nous avons DOT NET CORE et les goûts ...
Loudenvier

1
@Loudenvier - Beau travail là-dessus. Le vôtre a été le plus rapide de près de 400%! .Net Core est comme une amélioration gratuite des performances de 150 à 200%. Cela se rapproche des performances C ++ mais beaucoup plus facile à coder. Merci pour le commentaire.
Sunsetquest

1
Cela ne fait que des espaces, pas d'autres caractères d'espace blanc. Peut-être que vous voulez char.IsWhiteSpace (ch) au lieu de src [i] == '\ u0020'. Je remarque que cela a été modifié par la communauté. Est-ce qu'ils l'ont embrouillé?
Evil Pigeon

3

Je partage ce que j'utilise, car il semble que j'ai trouvé quelque chose de différent. Je l'utilise depuis un moment et c'est assez rapide pour moi. Je ne sais pas comment cela se compare aux autres. Je l'utilise dans un écrivain de fichier délimité et j'exécute de grandes tables de données un champ à la fois.

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

2

En utilisant le programme de test publié par Jon Skeet, j'ai essayé de voir si je pouvais obtenir une boucle écrite à la main pour fonctionner plus rapidement.
Je peux battre NormalizeWithSplitAndJoin à chaque fois, mais seulement battre NormalizeWithRegex avec des entrées de 1000, 5.

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

Je n'ai pas regardé le code machine produit par la gigue, mais je suppose que le problème est le temps pris par l'appel à StringBuilder.Append () et pour faire beaucoup mieux, il faudrait utiliser un code non sécurisé.

Donc Regex.Replace () est très rapide et difficile à battre !!


2

VB.NET

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

C #

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

Profitez de la puissance de LINQ = D


Exactement! Pour moi, c'est aussi l'approche la plus élégante. Donc pour mémoire, en C # ce serait:string.Join(" ", myString.Split(' ').Where(s => s != " ").ToArray())
Efrain

1
Amélioration mineure sur le Splitpour attraper tous les espaces et supprimer la Whereclause:myString.Split(null as char[], StringSplitOptions.RemoveEmptyEntries)
David

1
Regex regex = new Regex(@"\W+");
string outputString = regex.Replace(inputString, " ");

Cela remplace tous les caractères autres que des mots par des espaces. Ainsi, cela remplacerait également des éléments tels que les crochets et les guillemets, etc., ce qui pourrait ne pas être ce que vous voulez.
Herman

0

La plus petite solution:

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


0

Vous pouvez essayer ceci:

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

0

Les groupes de remplacement fournissent une approche d'implants résolvant le remplacement de plusieurs caractères d' espace blanc par le même seul:

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

Veuillez noter que le deuxième exemple reste unique \ntandis que la réponse acceptée remplacerait la fin de la ligne par un espace.

Si vous devez remplacer une combinaison de caractères d'espace blanc par le premier, supprimez simplement la référence arrière \kdu motif.


0

L'utilisation d'une expression régulière, pour remplacer 2 ou plusieurs espaces blancs par un seul espace, est également une bonne solution.

Nous utilisons le modèle regex comme « \ s + ».

  • \ s correspond à un espace, une tabulation, une nouvelle ligne, un retour chariot, un saut de page ou une tabulation verticale.

  • «+» indique une ou plusieurs occurrences.

Exemple de regex

String blogName = "  Sourav .  Pal.   "

 String nameWithProperSpacing = blogName.replaceAll("\\s+", " ");   
System.out.println( nameWithProperSpacing );

-1

Il n'y a aucun moyen intégré de faire cela. Vous pouvez essayer ceci:

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

Cela supprimera les blancs de début et de fin et réduira tout espace blanc interne en un seul caractère d'espacement. Si vous ne voulez vraiment réduire que les espaces, les solutions utilisant une expression régulière sont meilleures; sinon cette solution est meilleure. (Voir l' analyse effectuée par Jon Skeet.)


7
Si l'expression régulière est compilée et mise en cache, je ne suis pas sûr que cela ait plus de surcharge que le fractionnement et la jointure, ce qui pourrait créer des charges de chaînes intermédiaires. Avez-vous fait des repères minutieux des deux approches avant de supposer que votre chemin est plus rapide?
Jon Skeet

1
L'espace blanc n'est pas déclaré ici
Tim Hoolihan

3
En parlant de frais généraux, pourquoi diable appelez-vous source.ToCharArray()puis jetez-vous le résultat?
Jon Skeet

2
Et appeler ToCharArray()le résultat de string.Join, uniquement pour créer une nouvelle chaîne ... wow, pour que cela soit dans un post se plaignant de surcharge est tout simplement remarquable. -1.
Jon Skeet

1
Oh, et en supposant que whitespacec'est le cas new char[] { ' ' }, cela donnera un mauvais résultat si la chaîne d'entrée commence ou se termine par un espace.
Jon Skeet
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.