Ajouter des espaces avant les lettres majuscules

193

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

Voici ma tentative avec un RegEx

System.Text.RegularExpressions.Regex.Replace(value, "[A-Z]", " $0")
Bob
la source
2
Avez-vous une plainte particulière à formuler concernant l'approche que vous avez adoptée? Cela pourrait nous aider à améliorer votre méthode.
Blair Conrad
Si l'expression rationnelle fonctionne, je m'en tiendrai à cela. Regex est optimisé pour la manipulation de chaînes.
Michael Meadows
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 langues.
Bob
2
Votre code n'a tout simplement pas fonctionné car la chaîne modifiée est la valeur de retour de la fonction 'Remplacer'. Avec cette ligne de code: 'System.Text.RegularExpressions.Regex.Replace (value, "[AZ]", "$ 0"). Trim ();' cela fonctionnerait parfaitement. (Je commente simplement parce que je suis tombé sur ce post et que personne n'a vraiment vu ce qui n'allait pas avec votre code.)
Mattu475
Regex.Replace ("ThisStringHasNoSpacesButItDoesHaveCapitals", @ "\ B [AZ]", m => "" + m);
saquib adil

Réponses:

203

Les expressions rationnelles fonctionneront bien (j'ai même voté pour la réponse de Martin Browns), mais elles sont chères (et personnellement, je trouve n'importe quel modèle plus long que quelques caractères prohibitifs)

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

Le fera 100 000 fois en 2 968 750 ticks, le regex prendra 25 000 000 ticks (et c'est avec le regex compilé).

C'est mieux, pour une valeur donnée de meilleur (c'est-à-dire plus rapide) mais c'est plus de code à maintenir. «Mieux» est souvent un compromis d'exigences concurrentes.

J'espère que cela t'aides :)

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

Sur une chaîne avec 'Abbbbbbbbb' répété 100 fois (c.-à-d. 1 000 octets), une série de 100 000 conversions prend la fonction codée à la main 4 517 177 tics, et le Regex ci-dessous prend 59 435 719, ce qui rend la fonction codée à la main exécutée dans 7,6% du temps qu'il faut Regex.

Mise à jour 2 Tiendra-t-elle compte des acronymes? Ce sera maintenant! La logique de l'énoncé if est assez obscure, comme vous pouvez le voir élargir à cela ...

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 la méthode simple d' origine 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();
}
binaire Worrier
la source
8
if (char.IsUpper (text [i]) && text [i - 1]! = '') Si vous réexécutez le code ci-dessus, il continue d'ajouter des espaces, cela empêchera l'ajout d'espaces s'il y a un espace avant la capitale lettre.
Paul Talbot
Je ne suis pas sûr donc j'ai pensé que je demanderais, cette méthode gère-t-elle les acronymes comme décrit dans la réponse de Martin Brown "DriveIsSCSICompatible" deviendrait idéalement "Drive Is SCSI Compatible"
Coops
Cela a fait 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?
Coops
1
L'ajout d'une vérification pour char.IsLetter (texte [i + 1]) aide avec les acronymes avec des caractères spéciaux et des chiffres (c.-à-d. ABC_DEF ne sera pas divisé en AB C_DEF).
HeXanon
1
Je ne suis pas sûr que la partie acronymes soit correcte lorsqu'elle est désactivée. Je viens de lancer un test "ASentenceABC" se développe en "ASentence AB C". Devrait être "A Sentence AB C"
Tim Rutter
149

Votre solution a un problème en ce qu'elle met un espace avant la première lettre T afin que vous obteniez

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

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

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

Modifier 1:

Si vous l'utilisez, @"(\p{Ll})(\p{Lu})"il récupérera également les caractères accentués.

Modifier 2:

Si vos chaînes peuvent contenir des acronymes, vous pouvez utiliser ceci:

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

Ainsi, "DriveIsSCSICompatible" devient "Drive Is SCSI Compatible"

Martin Brown
la source
3
Ne pourriez-vous pas également conserver le RegEx d'origine et Trim () le résultat?
PandaWood
3
@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 façon de procéder.
Martin Brown
Pourriez-vous également utiliser "([^A-Z\\s])([A-Z])", même avec des acronymes?
Ruben9922
82

N'a pas testé les performances, mais ici en ligne avec linq:

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

Je sais que c'est une ancienne, mais c'est une extension que j'utilise lorsque je dois le faire:

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

Rob Hardy
la source
J'aime l'idée de ceci comme méthode d'extension, si vous l'ajoutez, TrimStart(' ')cela supprimera l'espace de tête.
user1069816
1
Merci @ user1069816. J'ai changé l'extension pour utiliser la surcharge SelectManyqui inclut un index, de cette façon, elle évite la première lettre et la surcharge potentielle inutile d'un appel supplémentaire à TrimStart(' '). Rob.
Rob Hardy
8

Bienvenue chez Unicode

Toutes ces solutions sont essentiellement erronées pour le texte moderne. Vous devez utiliser quelque chose qui comprend la casse. Puisque Bob a demandé d'autres langues, je vais en donner quelques-unes pour Perl.

Je propose quatre solutions, allant du pire au meilleur. Seul le meilleur a toujours raison. Les autres ont des problèmes. Voici un test 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é placés, et j'ai marqué comme mauvais tout ce qui est, bien, mauvais.

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 MisterDženanLjubović
     [WRONG]   Worst:    MisterDženanLjubović
     [WRONG]   Ok:       MisterDženanLjubović
               Better:   Mister_Dženan_Ljubović
               Best:     Mister_Dženan_Ljubović
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

BTW, presque tout le monde ici a choisi la première voie, celle marquée "Pire". Quelques-uns ont sélectionné la deuxième voie, marquée "OK". Mais personne d'autre avant moi ne vous a montré comment faire l'approche "meilleure" ou "meilleure".

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           ,
    $MisterDženanLjubović         ,
    $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
    MisterDženanLjubović
    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 pouvez obtenir 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'ai hâte de voir quelqu'un poster le bon code ℂ♯.

Je remarque que le code de surbrillance de StackOverflow est à nouveau misérablement stupide. Ils font tout de même le même vieux boiteux que (la plupart mais pas tous) du reste des mauvaises approches mentionnées ici. N'est-il pas grand temps de mettre 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 fait un mauvais code.

tchrist
la source
votre «meilleure» réponse semble la plus proche jusqu'à présent, mais il ne semble pas qu'elle tienne compte de la ponctuation principale ou des autres lettres majuscules non minuscules. Cela semble fonctionner le mieux pour moi (en java): replaceAll ("(? <= [^^ \\ p {javaUpperCase}]) (? = [\\ p {javaUpperCase}])", "");
Randyaa
Hmm. Je ne suis pas sûr que les chiffres romains devraient vraiment compter en majuscules dans cet exemple. L'exemple de modificateur de lettres ne devrait certainement pas être compté. Si vous allez sur McDonalds.com, vous verrez qu'il est écrit sans espace.
Martin Brown
Il convient également de noter que vous n'obtiendrez jamais cela pour être parfait. Par exemple, je voudrais voir un exemple qui trie "AlexandervonHumboldt", qui devrait finir par "Alexander von Humboldt". Ensuite, il y a bien sûr des langues qui n'ont pas la destination du capital et des minuscules.
Martin Brown
8

J'ai décidé de créer une méthode d'extension simple basée sur le code de Binary Worrier qui gérera correctement les acronymes et est reproductible (ne modifie pas les mots déjà espacés). Voici mon résultat.

public static string UnPascalCase(this string text)
{
    if (string.IsNullOrWhiteSpace(text))
        return "";
    var newText = new StringBuilder(text.Length * 2);
    newText.Append(text[0]);
    for (int i = 1; i < text.Length; i++)
    {
        var currentUpper = char.IsUpper(text[i]);
        var prevUpper = char.IsUpper(text[i - 1]);
        var nextUpper = (text.Length > i + 1) ? char.IsUpper(text[i + 1]) || char.IsWhiteSpace(text[i + 1]): prevUpper;
        var spaceExists = char.IsWhiteSpace(text[i - 1]);
        if (currentUpper && !spaceExists && (!nextUpper || !prevUpper))
                newText.Append(' ');
        newText.Append(text[i]);
    }
    return newText.ToString();
}

Voici les cas de tests unitaires que cette fonction réussit. J'ai ajouté la plupart des cas suggérés par tchrist à cette liste. Les trois de ceux qu'il ne réussit pas (deux ne sont que des chiffres romains) sont commentés:

Assert.AreEqual("For You And I", "ForYouAndI".UnPascalCase());
Assert.AreEqual("For You And The FBI", "ForYouAndTheFBI".UnPascalCase());
Assert.AreEqual("A Man A Plan A Canal Panama", "AManAPlanACanalPanama".UnPascalCase());
Assert.AreEqual("DNS Server", "DNSServer".UnPascalCase());
Assert.AreEqual("For You And I", "For You And I".UnPascalCase());
Assert.AreEqual("Mount Mᶜ Kinley National Park", "MountMᶜKinleyNationalPark".UnPascalCase());
Assert.AreEqual("El Álamo Tejano", "ElÁlamoTejano".UnPascalCase());
Assert.AreEqual("The Ævar Arnfjörð Bjarmason", "TheÆvarArnfjörðBjarmason".UnPascalCase());
Assert.AreEqual("Il Caffè Macchiato", "IlCaffèMacchiato".UnPascalCase());
//Assert.AreEqual("Mister Dženan Ljubović", "MisterDženanLjubović".UnPascalCase());
//Assert.AreEqual("Ole King Henry Ⅷ", "OleKingHenryⅧ".UnPascalCase());
//Assert.AreEqual("Carlos Ⅴº El Emperador", "CarlosⅤºElEmperador".UnPascalCase());
Assert.AreEqual("For You And The FBI", "For You And The FBI".UnPascalCase());
Assert.AreEqual("A Man A Plan A Canal Panama", "A Man A Plan A Canal Panama".UnPascalCase());
Assert.AreEqual("DNS Server", "DNS Server".UnPascalCase());
Assert.AreEqual("Mount Mᶜ Kinley National Park", "Mount Mᶜ Kinley National Park".UnPascalCase());
Kevin Stricker
la source
Semblable à une autre solution publiée ici, elle échoue avec la chaîne "RegularOTs". Il renvoie "Regular O Ts"
Patee Gutee
4

Binary Worrier, j'ai utilisé votre code suggéré, et il est plutôt bon, je n'ai qu'un ajout mineur:

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

                }
                newText.Append(result[i]);
            }
    return newText.ToString();
}

J'ai ajouté une condition !char.IsUpper(text[i - 1]). Cela a corrigé un bug qui faisait que quelque chose comme «AverageNOX» était transformé en «Average NO X», ce qui est évidemment faux, car il devrait se lire «Average NOX».

Malheureusement, cela a toujours le bug que si vous avez le texte «FromAStart», vous obtiendrez «From AStart».

Des réflexions sur la résolution de ce problème?

Richard Priddy
la source
Peut-être que quelque chose comme ça fonctionnerait: char.IsUpper (text [i]) && (char.IsLower (text [i - 1]) || (char.IsLower (text [i + 1]))
Martin Brown
1
C'est le bon: if (char.IsUpper(text[i]) && !(char.IsUpper(text[i - 1]) && char.IsUpper(text[i + 1])))Résultat du test: "From Start", "From THE Start", "From A Start" mais vous avez besoin i < text.Length - 1dans la condition de boucle for d'ignorer le dernier caractère et d'éviter les exceptions hors limites.
CallMeLaNN
Oh, c'est pareil. ! (a && b) et (! a ||! b) car inférieur =! supérieur.
CallMeLaNN
3

Voici la mienne:

private string SplitCamelCase(string s) 
{ 
    Regex upperCaseRegex = new Regex(@"[A-Z]{1}[a-z]*"); 
    MatchCollection matches = upperCaseRegex.Matches(s); 
    List<string> words = new List<string>(); 
    foreach (Match match in matches) 
    { 
        words.Add(match.Value); 
    } 
    return String.Join(" ", words.ToArray()); 
}
Cory Foy
la source
Est-ce censé être C #? Si oui, dans quel espace de noms se trouve List? Voulez-vous dire ArrayList ou List <string>?
Martin Brown
La liste <string> conviendrait parfaitement. Désolé pour ça.
Cory Foy
@Martin Il avait toujours la bonne syntaxe, elle était juste cachée dans un <pre><code>code</code></pre>bloc au lieu de la syntaxe Markdown. Pas besoin de le contrer (si c'était vous).
George Stocker
3

Assurez-vous que vous ne placez pas d' espaces au début de la chaîne, mais que vous les placez entre des majuscules consécutives. Certaines des réponses ici n'abordent pas l'un ou les deux de ces points. Il existe d'autres façons que l'expression régulière, mais si vous préférez l'utiliser, essayez ceci:

Regex.Replace(value, @"\B[A-Z]", " $0")

Le \Best un nié \b, il représente donc une frontière non mot. Cela signifie que le motif correspond à "Y" dans XYzabc, mais pas dans Yzabcou X Yzabc. En bonus, vous pouvez l'utiliser sur une chaîne avec des espaces et cela ne les doublera pas.

Justin Morgan
la source
3

Cette expression régulière place un caractère espace devant chaque lettre majuscule:

using System.Text.RegularExpressions;

const string myStringWithoutSpaces = "ThisIsAStringWithoutSpaces";
var myStringWithSpaces = Regex.Replace(myStringWithoutSpaces, "([A-Z])([a-z]*)", " $1$2");

Attention à l'espace devant si "$ 1 $ 2", c'est ce qui le fera.

Voici le résultat:

"This Is A String Without Spaces"
Matthias Thomann
la source
1
Si vous souhaitez que les nombres soient également séparés, utilisez plutôt ce modèle d'expression régulière:"([A-Z0-9])([a-z]*)"
Matthias Thomann
2

Ce que vous avez fonctionne parfaitement. N'oubliez pas de réaffecter valueà la valeur de retour de cette fonction.

value = System.Text.RegularExpressions.Regex.Replace(value, "[A-Z]", " $0");
Bill le lézard
la source
2

Voici comment vous pouvez le faire en SQL

create  FUNCTION dbo.PascalCaseWithSpace(@pInput AS VARCHAR(MAX)) RETURNS VARCHAR(MAX)
BEGIN
    declare @output varchar(8000)

set @output = ''


Declare @vInputLength        INT
Declare @vIndex              INT
Declare @vCount              INT
Declare @PrevLetter varchar(50)
SET @PrevLetter = ''

SET @vCount = 0
SET @vIndex = 1
SET @vInputLength = LEN(@pInput)

WHILE @vIndex <= @vInputLength
BEGIN
    IF ASCII(SUBSTRING(@pInput, @vIndex, 1)) = ASCII(Upper(SUBSTRING(@pInput, @vIndex, 1)))
       begin 

        if(@PrevLetter != '' and ASCII(@PrevLetter) = ASCII(Lower(@PrevLetter)))
            SET @output = @output + ' ' + SUBSTRING(@pInput, @vIndex, 1)
            else
            SET @output = @output +  SUBSTRING(@pInput, @vIndex, 1) 

        end
    else
        begin
        SET @output = @output +  SUBSTRING(@pInput, @vIndex, 1) 

        end

set @PrevLetter = SUBSTRING(@pInput, @vIndex, 1) 

    SET @vIndex = @vIndex + 1
END


return @output
END
KCITGuy
la source
2

Inspiré de @MartinBrown, Two Lines of Simple Regex, qui résoudra votre nom, y compris les acyronymes n'importe où dans la chaîne.

public string ResolveName(string name)
{
   var tmpDisplay = Regex.Replace(name, "([^A-Z ])([A-Z])", "$1 $2");
   return Regex.Replace(tmpDisplay, "([A-Z]+)([A-Z][^A-Z$])", "$1 $2").Trim();
}
johnny 5
la source
J'aime cette solution. C'est court et rapide. Cependant, semblable à d'autres solutions, il échoue avec la chaîne "RegularOTs". Chaque solution que j'ai essayée ici renvoie "Regular O Ts"
Patee Gutee
@PateeGutee l'OP voulait de l'espace avant les capitoles, il n'a pas mentionné les abréviations, nous avons un correctif pour cela dans la production cod
johnny 5
Pouvez-vous montrer le correctif? J'ai des chaînes comme celle-ci dans mes données et cela me donne un résultat incorrect. Merci.
Patee Gutee
@PateeGutee Désolé, j'ai mal lu ce que vous vouliez. La pluralisation est un autre problème, les "RegularOTs", à quoi vous attendez-vous "OT réguliers" ou "OT réguliers"
johnny 5
1
@PateeGutee J'ai mis à jour ma réponse pour vous, je pense que cela devrait fonctionner
johnny 5
1
replaceAll("(?<=[^^\\p{Uppercase}])(?=[\\p{Uppercase}])"," ");
Randyaa
la source
1
static string AddSpacesToColumnName(string columnCaption)
    {
        if (string.IsNullOrWhiteSpace(columnCaption))
            return "";
        StringBuilder newCaption = new StringBuilder(columnCaption.Length * 2);
        newCaption.Append(columnCaption[0]);
        int pos = 1;
        for (pos = 1; pos < columnCaption.Length-1; pos++)
        {               
            if (char.IsUpper(columnCaption[pos]) && !(char.IsUpper(columnCaption[pos - 1]) && char.IsUpper(columnCaption[pos + 1])))
                newCaption.Append(' ');
            newCaption.Append(columnCaption[pos]);
        }
        newCaption.Append(columnCaption[pos]);
        return newCaption.ToString();
    }
cyril
la source
1

En Ruby, via Regexp:

"FooBarBaz".gsub(/(?!^)(?=[A-Z])/, ' ') # => "Foo Bar Baz"
Artem
la source
1
Oops désolé. J'ai oublié que c'est une question spécifique à C # et j'ai posté ici la réponse de Ruby :(
Artem
1

J'ai pris l'excellente solution de Kevin Strikers et je me suis converti en VB. Étant donné que je suis verrouillé dans .NET 3.5, j'ai également dû écrire IsNullOrWhiteSpace. Cela passe tous ses tests.

<Extension()>
Public Function IsNullOrWhiteSpace(value As String) As Boolean
    If value Is Nothing Then
        Return True
    End If
    For i As Integer = 0 To value.Length - 1
        If Not Char.IsWhiteSpace(value(i)) Then
            Return False
        End If
    Next
    Return True
End Function

<Extension()>
Public Function UnPascalCase(text As String) As String
    If text.IsNullOrWhiteSpace Then
        Return String.Empty
    End If

    Dim newText = New StringBuilder()
    newText.Append(text(0))
    For i As Integer = 1 To text.Length - 1
        Dim currentUpper = Char.IsUpper(text(i))
        Dim prevUpper = Char.IsUpper(text(i - 1))
        Dim nextUpper = If(text.Length > i + 1, Char.IsUpper(text(i + 1)) Or Char.IsWhiteSpace(text(i + 1)), prevUpper)
        Dim spaceExists = Char.IsWhiteSpace(text(i - 1))
        If (currentUpper And Not spaceExists And (Not nextUpper Or Not prevUpper)) Then
            newText.Append(" ")
        End If
        newText.Append(text(i))
    Next
    Return newText.ToString()
End Function
Brad Irby
la source
1

La question est un peu ancienne mais de nos jours il existe une belle bibliothèque sur Nuget qui fait exactement cela ainsi que de nombreuses autres conversions en texte lisible par l'homme.

Découvrez Humanizer sur GitHub ou Nuget.

Exemple

"PascalCaseInputStringIsTurnedIntoSentence".Humanize() => "Pascal case input string is turned into sentence"
"Underscored_input_string_is_turned_into_sentence".Humanize() => "Underscored input string is turned into sentence"
"Underscored_input_String_is_turned_INTO_sentence".Humanize() => "Underscored input String is turned INTO sentence"

// acronyms are left intact
"HTML".Humanize() => "HTML"
Jonas Pegerfalk
la source
Je viens d'essayer cela et le premier lien est maintenant rompu. NuGet fonctionne, mais le package ne se compile pas dans ma solution. Une bonne idée, si ça marchait.
philw
1

Semble être une bonne occasion pour Aggregate. Ceci est conçu pour être lisible, pas nécessairement particulièrement rapide.

someString
.Aggregate(
   new StringBuilder(),
   (str, ch) => {
      if (char.IsUpper(ch) && str.Length > 0)
         str.Append(" ");
      str.Append(ch);
      return str;
   }
).ToString();
Dave Cousineau
la source
0

En plus de la réponse de Martin Brown, j'avais également un problème avec les chiffres. Par exemple: «Location2» ou «Jan22» doit être «Location 2» et «Jan 22» respectivement.

Voici mon expression régulière pour ce faire, en utilisant la réponse de Martin Brown:

"((?<=\p{Ll})\p{Lu})|((?!\A)\p{Lu}(?>\p{Ll}))|((?<=[\p{Ll}\p{Lu}])\p{Nd})|((?<=\p{Nd})\p{Lu})"

Voici quelques excellents sites pour comprendre ce que chaque partie signifie également:

Analyseur d'expressions régulières basé sur Java (mais fonctionne pour la plupart des expressions régulières .net)

Analyseur basé sur un script d'action

La regex ci-dessus ne fonctionnera pas sur le site de script d'action à moins que vous ne remplaciez tous les \p{Ll}avec [a-z], les \p{Lu}avec [A-Z]et \p{Nd}avec [0-9].

Daryl
la source
0

Voici ma solution, basée sur la suggestion de Binary Worriers et la construction dans les commentaires de Richard Priddys, mais tenant également compte du fait que des espaces blancs peuvent exister dans la chaîne fournie, de sorte qu'il n'ajoutera pas d'espace blanc à côté des espaces blancs existants.

public string AddSpacesBeforeUpperCase(string nonSpacedString)
    {
        if (string.IsNullOrEmpty(nonSpacedString))
            return string.Empty;

        StringBuilder newText = new StringBuilder(nonSpacedString.Length * 2);
        newText.Append(nonSpacedString[0]);

        for (int i = 1; i < nonSpacedString.Length; i++)
        {
            char currentChar = nonSpacedString[i];

            // If it is whitespace, we do not need to add another next to it
            if(char.IsWhiteSpace(currentChar))
            {
                continue;
            }

            char previousChar = nonSpacedString[i - 1];
            char nextChar = i < nonSpacedString.Length - 1 ? nonSpacedString[i + 1] : nonSpacedString[i];

            if (char.IsUpper(currentChar) && !char.IsWhiteSpace(nextChar) 
                && !(char.IsUpper(previousChar) && char.IsUpper(nextChar)))
            {
                newText.Append(' ');
            }
            else if (i < nonSpacedString.Length)
            {
                if (char.IsUpper(currentChar) && !char.IsWhiteSpace(nextChar) && !char.IsUpper(nextChar))
                {
                    newText.Append(' ');
                }
            }

            newText.Append(currentChar);
        }

        return newText.ToString();
    }
Yetiish
la source
0

Pour tous ceux qui recherchent une fonction C ++ répondant à cette même question, vous pouvez utiliser ce qui suit. Ceci est modélisé d'après la réponse donnée par @Binary Worrier. Cette méthode conserve simplement les acronymes automatiquement.

using namespace std;

void AddSpacesToSentence(string& testString)
        stringstream ss;
        ss << testString.at(0);
        for (auto it = testString.begin() + 1; it != testString.end(); ++it )
        {
            int index = it - testString.begin();
            char c = (*it);
            if (isupper(c))
            {
                char prev = testString.at(index - 1);
                if (isupper(prev))
                {
                    if (index < testString.length() - 1)
                    {
                        char next = testString.at(index + 1);
                        if (!isupper(next) && next != ' ')
                        {
                            ss << ' ';
                        }
                    }
                }
                else if (islower(prev)) 
                {
                   ss << ' ';
                }
            }

            ss << c;
        }

        cout << ss.str() << endl;

Les chaînes de test que j'ai utilisées pour cette fonction, et les résultats sont les suivants:

  • "helloWorld" -> "hello World"
  • "HelloWorld" -> "Hello World"
  • "HelloABCWorld" -> "Bonjour ABC World"
  • "HelloWorldABC" -> "Hello World ABC"
  • "ABCHelloWorld" -> "ABC Hello World"
  • "ABC HELLO WORLD" -> "ABC HELLO WORLD"
  • "ABCHELLOWORLD" -> "ABCHELLOWORLD"
  • "A" -> "A"
lbrendanl
la source
0

Une solution C # pour une chaîne d'entrée composée uniquement de caractères ASCII. La regex incorpore un lookbehind négatif pour ignorer une lettre majuscule (majuscule) qui apparaît au début de la chaîne. Utilise Regex.Replace () pour renvoyer la chaîne souhaitée.

Voir également la démo regex101.com .

using System;
using System.Text.RegularExpressions;

public class RegexExample
{
    public static void Main()
    {
        var text = "ThisStringHasNoSpacesButItDoesHaveCapitals";

        // Use negative lookbehind to match all capital letters
        // that do not appear at the beginning of the string.
        var pattern = "(?<!^)([A-Z])";

        var rgx = new Regex(pattern);
        var result = rgx.Replace(text, " $1");
        Console.WriteLine("Input: [{0}]\nOutput: [{1}]", text, result);
    }
}

Production attendue:

Input: [ThisStringHasNoSpacesButItDoesHaveCapitals]
Output: [This String Has No Spaces But It Does Have Capitals]

Mise à jour: voici une variante qui traitera également les acronymes (séquences de lettres majuscules).

Voir également la démo regex101.com et la démo ideone.com .

using System;
using System.Text.RegularExpressions;

public class RegexExample
{
    public static void Main()
    {
        var text = "ThisStringHasNoSpacesASCIIButItDoesHaveCapitalsLINQ";

        // Use positive lookbehind to locate all upper-case letters
        // that are preceded by a lower-case letter.
        var patternPart1 = "(?<=[a-z])([A-Z])";

        // Used positive lookbehind and lookahead to locate all
        // upper-case letters that are preceded by an upper-case
        // letter and followed by a lower-case letter.
        var patternPart2 = "(?<=[A-Z])([A-Z])(?=[a-z])";

        var pattern = patternPart1 + "|" + patternPart2;
        var rgx = new Regex(pattern);
        var result = rgx.Replace(text, " $1$2");

        Console.WriteLine("Input: [{0}]\nOutput: [{1}]", text, result);
    }
}

Production attendue:

Input: [ThisStringHasNoSpacesASCIIButItDoesHaveCapitalsLINQ]
Output: [This String Has No Spaces ASCII But It Does Have Capitals LINQ]
DavidRR
la source
0

Voici une solution plus approfondie qui ne met pas d'espaces devant les mots:

Remarque: j'ai utilisé plusieurs expressions régulières (pas concises, mais elle traitera également les acronymes et les mots d'une seule lettre)

Dim s As String = "ThisStringHasNoSpacesButItDoesHaveCapitals"
s = System.Text.RegularExpressions.Regex.Replace(s, "([a-z])([A-Z](?=[A-Z])[a-z]*)", "$1 $2")
s = System.Text.RegularExpressions.Regex.Replace(s, "([A-Z])([A-Z][a-z])", "$1 $2")
s = System.Text.RegularExpressions.Regex.Replace(s, "([a-z])([A-Z][a-z])", "$1 $2")
s = System.Text.RegularExpressions.Regex.Replace(s, "([a-z])([A-Z][a-z])", "$1 $2") // repeat a second time

Dans :

"ThisStringHasNoSpacesButItDoesHaveCapitals"
"IAmNotAGoat"
"LOLThatsHilarious!"
"ThisIsASMSMessage"

Sortie :

"This String Has No Spaces But It Does Have Capitals"
"I Am Not A Goat"
"LOL Thats Hilarious!"
"This Is ASMS Message" // (Difficult to handle single letter words when they are next to acronyms.)
CrazyTim
la source
Cela produit "Cette chaîne n'a pas d'espaces mais elle a des capitales"
Andy Robinson
Salut @AndyRobinson, merci. J'ai changé pour utiliser plusieurs remplacements Regex. Je ne sais pas s'il existe un moyen plus concis, mais cela fonctionne maintenant.
CrazyTim
0

Toutes les réponses précédentes semblaient trop compliquées.

J'avais une chaîne qui avait un mélange de majuscules et de _ donc utilisé, string.Replace () pour faire le _, "" et utilisé ce qui suit pour ajouter un espace en majuscules.

for (int i = 0; i < result.Length; i++)
{
    if (char.IsUpper(result[i]))
    {
        counter++;
        if (i > 1) //stops from adding a space at if string starts with Capital
        {
            result = result.Insert(i, " ");
            i++; //Required** otherwise stuck in infinite 
                 //add space loop over a single capital letter.
        }
    }
}
st3_121
la source
0

Inspiré par la réponse de Binary Worrier, j'ai pris un élan à cela.

Voici le résultat:

/// <summary>
/// String Extension Method
/// Adds white space to strings based on Upper Case Letters
/// </summary>
/// <example>
/// strIn => "HateJPMorgan"
/// preserveAcronyms false => "Hate JP Morgan"
/// preserveAcronyms true => "Hate JPMorgan"
/// </example>
/// <param name="strIn">to evaluate</param>
/// <param name="preserveAcronyms" >determines saving acronyms (Optional => false) </param>
public static string AddSpaces(this string strIn, bool preserveAcronyms = false)
{
    if (string.IsNullOrWhiteSpace(strIn))
        return String.Empty;

    var stringBuilder = new StringBuilder(strIn.Length * 2)
        .Append(strIn[0]);

    int i;

    for (i = 1; i < strIn.Length - 1; i++)
    {
        var c = strIn[i];

        if (Char.IsUpper(c) && (Char.IsLower(strIn[i - 1]) || (preserveAcronyms && Char.IsLower(strIn[i + 1]))))
            stringBuilder.Append(' ');

        stringBuilder.Append(c);
    }

    return stringBuilder.Append(strIn[i]).ToString();
}

A testé en utilisant un chronomètre exécutant 10000000 itérations et différentes longueurs de chaîne et combinaisons.

En moyenne 50% (peut-être un peu plus) plus rapidement que la réponse Binary Worrier.

João Sequeira
la source
0
    private string GetProperName(string Header)
    {
        if (Header.ToCharArray().Where(c => Char.IsUpper(c)).Count() == 1)
        {
            return Header;
        }
        else
        {
            string ReturnHeader = Header[0].ToString();
            for(int i=1; i<Header.Length;i++)
            {
                if (char.IsLower(Header[i-1]) && char.IsUpper(Header[i]))
                {
                    ReturnHeader += " " + Header[i].ToString();
                }
                else
                {
                    ReturnHeader += Header[i].ToString();
                }
            }

            return ReturnHeader;
        }

        return Header;
    }
Hareendra Donapati
la source
0

Celui-ci comprend des acronymes et des pluriels d'acronymes et est un peu plus rapide que la réponse acceptée:

public string Sentencify(string value)
{
    if (string.IsNullOrWhiteSpace(value))
        return string.Empty;

    string final = string.Empty;
    for (int i = 0; i < value.Length; i++)
    {
        if (i != 0 && Char.IsUpper(value[i]))
        {
            if (!Char.IsUpper(value[i - 1]))
                final += " ";
            else if (i < (value.Length - 1))
            {
                if (!Char.IsUpper(value[i + 1]) && !((value.Length >= i && value[i + 1] == 's') ||
                                                     (value.Length >= i + 1 && value[i + 1] == 'e' && value[i + 2] == 's')))
                    final += " ";
            }
        }

        final += value[i];
    }

    return final;
}

Réussit ces tests:

string test1 = "RegularOTs";
string test2 = "ThisStringHasNoSpacesASCIIButItDoesHaveCapitalsLINQ";
string test3 = "ThisStringHasNoSpacesButItDoesHaveCapitals";
Serj Sagan
la source
la réponse acceptée concerne le cas où la valeur est nulle
Chris F Carroll
Cela ajoute un espace supplémentaire devant la sortie, c'est-à-dire HireDate => "Hire Date". Besoin d'une finale.TrimStart ou quelque chose. Je pense que c'est ce que l'une des autres réponses indique ci-dessous, mais en raison de la réorganisation, je ne sais pas s'il vous parlait, car sa réponse est basée sur RegEx.
b_levitt
Bonne capture ... aurait dû ajouter un marqueur de début et de fin à mes tests ... corrigé maintenant.
Serj Sagan
Semblable à une autre solution publiée ici, elle échoue avec la chaîne "RegularOTs". Il renvoie "Regular O Ts"
Patee Gutee
Merci d'avoir fait apparaître les pluriels d'abréviations, j'ai également mis à jour pour travailler pour cela.
Serj Sagan
0

Une implémentation avec fold, également connue sous le nom de Aggregate:

    public static string SpaceCapitals(this string arg) =>
       new string(arg.Aggregate(new List<Char>(),
                      (accum, x) => 
                      {
                          if (Char.IsUpper(x) &&
                              accum.Any() &&
                              // prevent double spacing
                              accum.Last() != ' ' &&
                              // prevent spacing acronyms (ASCII, SCSI)
                              !Char.IsUpper(accum.Last()))
                          {
                              accum.Add(' ');
                          }

                          accum.Add(x);

                          return accum;
                      }).ToArray());

En plus de la demande, cette implémentation enregistre correctement les espaces et acronymes de début, intérieurs et finaux, par exemple,

" SpacedWord " => " Spaced Word ",  

"Inner Space" => "Inner Space",  

"SomeACRONYM" => "Some ACRONYM".
Artru
la source
0

Un moyen simple d'ajouter des espaces après les minuscules, les majuscules ou les chiffres.

    string AddSpacesToSentence(string value, bool spaceLowerChar = true, bool spaceDigitChar = true, bool spaceSymbolChar = false)
    {
        var result = "";

        for (int i = 0; i < value.Length; i++)
        {
            char currentChar = value[i];
            char nextChar = value[i < value.Length - 1 ? i + 1 : value.Length - 1];

            if (spaceLowerChar && char.IsLower(currentChar) && !char.IsLower(nextChar))
            {
                result += value[i] + " ";
            }
            else if (spaceDigitChar && char.IsDigit(currentChar) && !char.IsDigit(nextChar))
            {
                result += value[i] + " ";
            }
            else if(spaceSymbolChar && char.IsSymbol(currentChar) && !char.IsSymbol(nextChar))
            {
                result += value[i];
            }
            else
            {
                result += value[i];
            }
        }

        return result;
    }
Prince Owusu
la source
1
Les réponses uniquement codées sont déconseillées. Veuillez cliquer sur modifier et ajouter quelques mots résumant la façon dont votre code répond à la question, ou peut-être expliquer en quoi votre réponse diffère de la ou des réponses précédentes. De l'avis
Nick