209 votes

Ajouter des espaces avant les lettres majuscules

Étant donné la chaîne "ThisStringHasNoSpacesButItDoesHaveCapitals", quelle est la meilleure façon d'ajouter des espaces avant les lettres majuscules. Ainsi, la chaîne finale serait "Cette chaîne n'a pas d'espace mais elle a des majuscules".

Voici ma tentative avec un RegEx

System.Text.RegularExpressions.Regex.Replace(value, "[A-Z]", " $0")

3 votes

Avez-vous une plainte particulière concernant l'approche que vous avez adoptée ? Cela pourrait nous aider à améliorer votre méthode.

0 votes

Si la regex fonctionne, alors je m'en tiendrais à cela. La regex est optimisée pour la manipulation des chaînes de caractères.

0 votes

Je suis simplement curieux de savoir s'il existe une meilleure approche ou peut-être même une approche intégrée. Je serais même curieux de voir d'autres approches avec d'autres langages.

212voto

Binary Worrier Points 27424

Les regex fonctionneront très bien (j'ai même voté pour la réponse de Martin Browns), mais ils sont coûteux (et personnellement, je trouve tout modèle plus long que quelques caractères prohibitif).

Cette fonction

string AddSpacesToSentence(string text, bool preserveAcronyms)
{
        if (string.IsNullOrWhiteSpace(text))
           return string.Empty;
        StringBuilder newText = new StringBuilder(text.Length * 2);
        newText.Append(text[0]);
        for (int i = 1; i < text.Length; i++)
        {
            if (char.IsUpper(text[i]))
                if ((text[i - 1] != ' ' && !char.IsUpper(text[i - 1])) ||
                    (preserveAcronyms && char.IsUpper(text[i - 1]) && 
                     i < text.Length - 1 && !char.IsUpper(text[i + 1])))
                    newText.Append(' ');
            newText.Append(text[i]);
        }
        return newText.ToString();
}

Il le fera 100 000 fois en 2 968 750 ticks, la regex prendra 25 000 000 ticks (et cela avec la regex compilée).

C'est mieux, pour une valeur donnée de mieux (c'est-à-dire plus rapide), mais c'est plus de code à maintenir. Le "mieux" est souvent un compromis entre des exigences concurrentes.

J'espère que cela vous aidera :)

Mise à jour
Cela fait un bon moment que je n'ai pas regardé ça, et je viens de réaliser que les timings n'ont pas été mis à jour depuis que le code a changé (il a seulement un peu changé).

Sur une chaîne de caractères contenant 'Abbbbbbbbb' répétée 100 fois (soit 1 000 octets), une exécution de 100 000 conversions prend 4 517 177 ticks à la fonction codée à la main, et 59 435 719 ticks à la Regex ci-dessous, ce qui fait que la fonction codée à la main s'exécute en 7,6 % du temps de la Regex.

Mise à jour 2 Prendra-t-il en compte les acronymes ? Il le fera maintenant ! La logique de l'instruction if est assez obscure, comme vous pouvez le voir en l'étendant à ceci ...

if (char.IsUpper(text[i]))
    if (char.IsUpper(text[i - 1]))
        if (preserveAcronyms && i < text.Length - 1 && !char.IsUpper(text[i + 1]))
            newText.Append(' ');
        else ;
    else if (text[i - 1] != ' ')
        newText.Append(' ');

... n'aide pas du tout !

Voici l'original simple une méthode qui ne se soucie pas des acronymes

string AddSpacesToSentence(string text)
{
        if (string.IsNullOrWhiteSpace(text))
           return "";
        StringBuilder newText = new StringBuilder(text.Length * 2);
        newText.Append(text[0]);
        for (int i = 1; i < text.Length; i++)
        {
            if (char.IsUpper(text[i]) && text[i - 1] != ' ')
                newText.Append(' ');
            newText.Append(text[i]);
        }
        return newText.ToString();
}

10 votes

If (char.IsUpper (text [i]) && text[i - 1] != ' ') Si vous réexécutez le code ci-dessus, il continue à ajouter des espaces, ceci empêchera l'ajout d'espaces s'il y a un espace avant la lettre majuscule.

0 votes

Je ne suis pas sûr, alors j'ai pensé demander si cette méthode gère les acronymes comme décrit dans la réponse de Martin Brown "DriveIsSCSICompatible" deviendrait idéalement "Drive Is SCSI Compatible".

0 votes

Cela a donné 1 caractère en remplaçant le contenu de votre instruction for par les instructions if nouvellement mises à jour, je fais peut-être quelque chose de mal ?

164voto

Martin Brown Points 8593

Votre solution présente un problème dans la mesure où elle met un espace avant la première lettre T, de sorte que vous obtenez

" This String..." instead of "This String..."

Pour contourner ce problème, cherchez la lettre minuscule qui la précède également et insérez l'espace au milieu :

newValue = Regex.Replace(value, "([a-z])([A-Z])", "$1 $2");

Edit 1 :

Si vous utilisez @"(\p{Ll})(\p{Lu})" il captera également les caractères accentués.

Edit 2 :

Si vos chaînes de caractères peuvent contenir des acronymes, vous pouvez l'utiliser :

newValue = Regex.Replace(value, @"((?<=\p{Ll})\p{Lu})|((?!\A)\p{Lu}(?>\p{Ll}))", " $0");

Donc "DriveIsSCSICompatible" devient "Drive Is SCSI Compatible".

6 votes

Ne pourriez-vous pas également conserver la RegEx originale et Trim() le résultat ?

4 votes

@PandaWood vous pourriez mais cela nécessiterait une autre allocation de mémoire et une copie de chaîne. Cela dit, si les performances sont un souci, une Regex n'est probablement pas la meilleure solution de toute façon.

0 votes

Pourriez-vous également utiliser "([^A-Z\\s])([A-Z])" même avec des acronymes ?

92voto

EtienneT Points 1552

Je n'ai pas testé les performances, mais ici en une ligne avec linq :

var val = "ThisIsAStringToTest";
val = string.Concat(val.Select(x => Char.IsUpper(x) ? " " + x : x.ToString())).TrimStart(' ');

0 votes

Merci pour cela !

22voto

Rob Hardy Points 855

Je sais que c'est une vieille histoire, mais c'est une extension que j'utilise quand j'ai besoin de faire ça :

public static class Extensions
{
    public static string ToSentence( this string Input )
    {
        return new string(Input.SelectMany((c, i) => i > 0 && char.IsUpper(c) ? new[] { ' ', c } : new[] { c }).ToArray());
    }
}

Cela vous permettra d'utiliser MyCasedString.ToSentence()

0 votes

J'aime l'idée de cette méthode en tant que méthode d'extension, si vous ajoutez TrimStart(' ') il supprimera l'espace de tête.

1 votes

Merci @user1069816. J'ai changé l'extension pour utiliser la surcharge de SelectMany qui comprend un index, ce qui permet d'éviter la première lettre et la surcharge potentielle inutile d'un appel supplémentaire à la fonction TrimStart(' ') . Rob.

9voto

tchrist Points 47116

Bienvenue à Unicode

Toutes ces solutions sont essentiellement erronées pour les textes modernes. Vous devez utiliser quelque chose qui comprend la casse. Puisque Bob a demandé d'autres langages, je vais en donner quelques-uns pour Perl.

Je propose quatre solutions, de la pire à la meilleure. Seule la meilleure est toujours la bonne. Les autres ont des problèmes. Voici un essai pour vous montrer ce qui fonctionne et ce qui ne fonctionne pas, et où. J'ai utilisé des traits de soulignement pour que vous puissiez voir où les espaces ont été mis, et j'ai marqué comme faux tout ce qui est, eh bien, faux.

Testing TheLoneRanger
               Worst:    The_Lone_Ranger
               Ok:       The_Lone_Ranger
               Better:   The_Lone_Ranger
               Best:     The_Lone_Ranger
Testing MountMKinleyNationalPark
     [WRONG]   Worst:    Mount_MKinley_National_Park
     [WRONG]   Ok:       Mount_MKinley_National_Park
     [WRONG]   Better:   Mount_MKinley_National_Park
               Best:     Mount_M_Kinley_National_Park
Testing ElÁlamoTejano
     [WRONG]   Worst:    ElÁlamo_Tejano
               Ok:       El_Álamo_Tejano
               Better:   El_Álamo_Tejano
               Best:     El_Álamo_Tejano
Testing TheÆvarArnfjörðBjarmason
     [WRONG]   Worst:    TheÆvar_ArnfjörðBjarmason
               Ok:       The_Ævar_Arnfjörð_Bjarmason
               Better:   The_Ævar_Arnfjörð_Bjarmason
               Best:     The_Ævar_Arnfjörð_Bjarmason
Testing IlCaffèMacchiato
     [WRONG]   Worst:    Il_CaffèMacchiato
               Ok:       Il_Caffè_Macchiato
               Better:   Il_Caffè_Macchiato
               Best:     Il_Caffè_Macchiato
Testing Misterenanubovi
     [WRONG]   Worst:    Misterenanubovi
     [WRONG]   Ok:       Misterenanubovi
               Better:   Mister_enan_ubovi
               Best:     Mister_enan_ubovi
Testing OleKingHenry
     [WRONG]   Worst:    Ole_King_Henry
     [WRONG]   Ok:       Ole_King_Henry
     [WRONG]   Better:   Ole_King_Henry
               Best:     Ole_King_Henry_
Testing CarlosºElEmperador
     [WRONG]   Worst:    CarlosºEl_Emperador
     [WRONG]   Ok:       Carlosº_El_Emperador
     [WRONG]   Better:   Carlosº_El_Emperador
               Best:     Carlos_º_El_Emperador

D'ailleurs, presque tout le monde ici a choisi la première méthode, celle qui est marquée "Pire". Quelques-uns ont choisi la deuxième méthode, marquée "OK". Mais personne avant moi ne vous a montré comment faire la "meilleure" ou la "meilleure" approche.

Voici le programme de test avec ses quatre méthodes :

#!/usr/bin/env perl
use utf8;
use strict;
use warnings;

# First I'll prove these are fine variable names:
my (
    $TheLoneRanger              ,
    $MountMKinleyNationalPark  ,
    $ElÁlamoTejano              ,
    $TheÆvarArnfjörðBjarmason   ,
    $IlCaffèMacchiato           ,
    $Misterenanubovi         ,
    $OleKingHenry              ,
    $CarlosºElEmperador        ,
);

# Now I'll load up some string with those values in them:
my @strings = qw{
    TheLoneRanger
    MountMKinleyNationalPark
    ElÁlamoTejano
    TheÆvarArnfjörðBjarmason
    IlCaffèMacchiato
    Misterenanubovi
    OleKingHenry
    CarlosºElEmperador
};

my($new, $best, $ok);
my $mask = "  %10s   %-8s  %s\n";

for my $old (@strings) {
    print "Testing $old\n";
    ($best = $old) =~ s/(?<=\p{Lowercase})(?=[\p{Uppercase}\p{Lt}])/_/g;

    ($new = $old) =~ s/(?<=[a-z])(?=[A-Z])/_/g;
    $ok = ($new ne $best) && "[WRONG]";
    printf $mask, $ok, "Worst:", $new;

    ($new = $old) =~ s/(?<=\p{Ll})(?=\p{Lu})/_/g;
    $ok = ($new ne $best) && "[WRONG]";
    printf $mask, $ok, "Ok:", $new;

    ($new = $old) =~ s/(?<=\p{Ll})(?=[\p{Lu}\p{Lt}])/_/g;
    $ok = ($new ne $best) && "[WRONG]";
    printf $mask, $ok, "Better:", $new;

    ($new = $old) =~ s/(?<=\p{Lowercase})(?=[\p{Uppercase}\p{Lt}])/_/g;
    $ok = ($new ne $best) && "[WRONG]";
    printf $mask, $ok, "Best:", $new;
}

Lorsque vous obtiendrez le même score que le "meilleur" sur cet ensemble de données, vous saurez que vous l'avez fait correctement. Jusque là, vous ne l'avez pas fait. Personne d'autre ici n'a fait mieux que "Ok", et la plupart l'ont fait "Pire". J'attends avec impatience de voir quelqu'un poster le code correct.

Je remarque que le code de mise en évidence de StackOverflow est à nouveau misérablement stupide. Ils font tous les mêmes vieux trucs boiteux que (la plupart mais pas tous) le reste des approches pauvres mentionnées ici ont fait. N'est-il pas grand temps de mettre l'ASCII au repos ? Cela n'a plus de sens, et prétendre que c'est tout ce que vous avez est tout simplement faux. Cela donne un mauvais code.

0 votes

Votre réponse "Best" semble la plus proche jusqu'à présent, mais elle ne semble pas tenir compte de la ponctuation ou d'autres lettres non minuscules en tête. Voici ce qui semble fonctionner le mieux pour moi (en java) : replaceAll("(?<=[^^ \\p {javaUpperCase}])(?=[ \\p {javaUpperCase}])", " ") ;

0 votes

Hmm. Je ne suis pas sûr que les chiffres romains devraient vraiment compter comme des majuscules dans cet exemple. L'exemple de la lettre modificatrice ne devrait définitivement pas être compté. Si vous allez sur McDonalds.com, vous verrez qu'elle est écrite sans espace.

0 votes

Il convient également de noter que vous ne parviendrez jamais à obtenir un résultat parfait. Par exemple, j'aimerais voir un exemple qui trie "AlexandervonHumboldt", qui devrait aboutir à "Alexander von Humboldt". Et puis, il y a bien sûr les langues qui ne connaissent pas la distinction entre majuscules et minuscules.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X