Étant donné la chaîne "ThisStringHasNoSpacesButItDoesHaveCapitals", quel est le meilleur moyen d'ajouter des espaces avant les majuscules. Donc, 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")
Les expressions rationnelles fonctionneront bien (j'ai même voté pour la réponse de Martin Brown), mais elles coûtent cher (et personnellement, je trouve n'importe quel motif 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();
}
Fera-le 100 000 fois en 2 968 750 tiques, la regex prendra 25 000 000 tiques (et c'est avec la regex compilée).
C’est mieux, pour une valeur donnée de mieux (c’est-à-dire plus vite) mais c’est plus de code à maintenir. "Mieux" est souvent un compromis entre des exigences concurrentes.
J'espère que cela t'aides :)
Mettre à jour
Cela fait un bon bout de temps que je n’y ai pas regardé, et je viens de me rendre compte 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 contenant 'Abbbbbbbbb' répétée 100 fois (soit 1 000 octets), une série de 100 000 conversions prend la fonction codée à la main 4 517 177 ticks, et l'expression régulière ci-dessous prend 59 435 719, ce qui rend la fonction codée à la main exécutée dans 7,6% du temps Regex.
Mise à jour 2 Prendra-t-il en compte les acronymes? Il va maintenant! La logique de la déclaration si 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 originale simple qui ne s'inquiète 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();
}
Votre solution a un problème en ce sens qu’elle place un espace avant la première lettre T afin que vous obteniez
" This String..." instead of "This String..."
Pour contourner ce problème, cherchez également la lettre minuscule qui le précède, puis insérez l'espace au milieu:
newValue = Regex.Replace(value, "([a-z])([A-Z])", "$1 $2");
Modifier 1:
Si vous utilisez @"(\p{Ll})(\p{Lu})"
, il récupérera également les caractères accentués.
Edit 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");
Donc, "DriveIsSCSICompatible" devient "Le lecteur est compatible SCSI"
N'a pas testé les performances, mais en une ligne avec linq:
var val = "ThisIsAStringToTest";
val = string.Concat(val.Select(x => Char.IsUpper(x) ? " " + x : x.ToString())).TrimStart(' ');
Je sais que c'est un ancien, 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()
Toutes ces solutions sont essentiellement fausses pour le texte moderne. Vous devez utiliser quelque chose qui comprend le cas. Puisque Bob a demandé d'autres langues, je vais en donner un 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 faux tout ce qui est faux.
Testing TheLoneRanger
Worst: The_Lone_Ranger
Ok: The_Lone_Ranger
Better: The_Lone_Ranger
Best: The_Lone_Ranger
Testing MountMᶜKinleyNationalPark
[WRONG] Worst: Mount_MᶜKinley_National_Park
[WRONG] Ok: Mount_MᶜKinley_National_Park
[WRONG] Better: Mount_MᶜKinley_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 sélectionné la première façon, celle marquée "Worst". Quelques-uns ont sélectionné la deuxième voie, marquée "OK". Mais personne avant moi ne vous a montré comment utiliser l'approche "Meilleur" ou "Meilleur".
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 ,
$MountMᶜKinleyNationalPark ,
$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
MountMᶜKinleyNationalPark
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 pourrez obtenir le même résultat que "Meilleur" sur cet ensemble de données, vous saurez que vous l’avez fait correctement. Jusque-là, vous ne le faites pas. Personne d'autre ici n'a fait mieux que "Ok", et la plupart l'ont fait "Pire". Je suis impatient de voir quelqu'un publier le code ℂ♯ correct.
Je remarque que le code de surbrillance de StackOverflow est misérablement misérable à nouveau. Ils font tous les mêmes vieux boiteux que (la plupart mais pas tous) le reste des approches pauvres mentionnées ici. N'est-il pas temps de mettre ASCII au repos? Cela n’a plus de sens, et prétendre que tout ce que vous avez est tout simplement faux. Cela crée un mauvais code.
Je me suis attaché à créer une méthode d'extension simple basée sur le code de Binary Worrier, qui gérera correctement les acronymes et qui est répétable (ne permet pas de changer 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 auxquels cette fonction a réussi. J'ai ajouté la plupart des cas suggérés par tchrist à cette liste. Les trois de ceux qu'il ne passe 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());
Binary Worrier, j'ai utilisé votre code suggéré, et il est plutôt bon, je n'ai qu'un ajout mineur à cela:
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 corrigeait un bug qui pouvait transformer quelque chose comme 'AverageNOX' en 'Average N O X', ce qui est évidemment faux, car il devrait se lire 'Average NOX'.
Malheureusement, il y a toujours le bogue voulant que si vous avez le texte «FromAStart», vous obtiendrez «From AStart».
Des idées sur la résolution de ce problème?
Voilà le mien:
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());
}
Assurez-vous que ne sont pas en plaçant des espaces au début de la chaîne, mais vous les mettez entre des majuscules consécutives. Certaines des réponses ici ne traitent pas de l'un ou l'autre de ces points. Il existe d'autres moyens que regex, mais si vous préférez l'utiliser, essayez ceci:
Regex.Replace(value, @"\B[A-Z]", " $0")
Le \B
est un \b
négatif, il représente donc une limite autre que Word. Cela signifie que le modèle correspond à "Y" dans XYzabc
, mais pas dans Yzabc
ou X Yzabc
. En prime, vous pouvez l’utiliser sur une chaîne contenant des espaces et elle ne les doublera pas.
Ce que vous avez fonctionne parfaitement. Rappelez-vous simplement de réaffecter value
à la valeur de retour de cette fonction.
value = System.Text.RegularExpressions.Regex.Replace(value, "[A-Z]", " $0");
Inspiré de @MartinBrown, Deux lignes de regex simple, qui résoudront votre nom, y compris les noms d'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();
}
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
Cette regex place un caractère d'espace devant chaque lettre majuscule:
using System.Text.RegularExpressions;
const string myStringWithoutSpaces = "ThisIsAStringWithoutSpaces";
var myStringWithSpaces = Regex.Replace(myStringWithoutSpaces, "([A-Z])([a-z]*)", " $1$2");
Faites attention à l’espace en face si "$ 1 $ 2", c’est ce qui sera fait.
C'est le résultat:
"This Is A String Without Spaces"
En Ruby, via Regexp:
"FooBarBaz".gsub(/(?!^)(?=[A-Z])/, ' ') # => "Foo Bar Baz"
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();
}
replaceAll("(?<=[^^\\p{Uppercase}])(?=[\\p{Uppercase}])"," ");
J'ai pris Kevin Strikers excellente solution et converti en VB. Depuis que je suis bloqué dans .NET 3.5, je devais aussi é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
Cela semble être une bonne opportunité 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();
Un moyen simple d'ajouter des espaces après les lettres minuscules, 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;
}
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;
}
En plus de la réponse de Martin Brown, j'avais également un problème avec les chiffres. Par exemple: "Emplacement2" ou "Jan22" devrait être "Emplacement 2" et "22 janvier" respectivement.
Voici mon expression régulière pour le 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 sites intéressants pour comprendre la signification de chaque partie:
Analyseur d'expressions régulières basé sur Java (mais fonctionne pour la plupart des regex .net)
La regex ci-dessus ne fonctionnera pas sur le site de script d'action à moins que vous ne remplaciez le \p{Ll}
par le [a-z]
, le \p{Lu}
par le [A-Z]
et le \p{Nd}
par le [0-9]
.
Toutes les réponses précédentes semblaient trop compliquées.
J'avais string qui avait un mélange de majuscules et de _ donc utilisé, string.Replace () pour créer le _, "" et a utilisé les éléments suivants pour ajouter un espace en majuscule.
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.
}
}
}
Pour ceux qui recherchent une fonction C++ répondant à la même question, vous pouvez utiliser ce qui suit. Ceci est calqué sur la réponse donnée par @Binary Worrier. Cette méthode ne fait que conserver 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 tests que j'ai utilisées pour cette fonction et les résultats sont les suivants:
Voici une solution plus complète qui ne place pas d'espaces devant les mots:
Remarque: J'ai utilisé plusieurs expressions rationnelles (pas concis, mais il gérera également les acronymes et les mots composés 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"
En dehors :
"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.)
Une implémentation avec fold
, également appelée 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, de fin, par exemple,
" SpacedWord " => " Spaced Word ",
"Inner Space" => "Inner Space",
"SomeACRONYM" => "Some ACRONYM".
Celui-ci comprend les acronymes et les pluriels d’acronyme 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;
}
Passe ces tests:
string test1 = "RegularOTs";
string test2 = "ThisStringHasNoSpacesASCIIButItDoesHaveCapitalsLINQ";
string test3 = "ThisStringHasNoSpacesButItDoesHaveCapitals";
Voici ma solution, basée sur la suggestion de Binary Worriers et sur l’intégration des commentaires de Richard Priddys, mais en tenant également compte du fait qu’un espace blanc peut exister dans la chaîne fournie afin qu’il n’ajoute pas d’espace blanc à côté d’un espace blanc existant.
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();
}
Solution C # pour une chaîne d'entrée composée uniquement de caractères ASCII. Regex incorpore negative lookbehind pour ignorer une lettre majuscule (majuscule) qui apparaît au début de la chaîne. Utilise Regex.Replace () pour retourner la chaîne désirée.
Voir aussi démo de 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]
Update: Voici une variante qui gérera également acronymes (séquences de lettres majuscules).
Voir aussi demo regex101.com et demo 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]
Inspiré par la réponse binaire plus inquiétante, j’ai pris un élan à cet égard.
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();
}
Test effectué avec 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 vite que la réponse Binary Worrier.
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 on 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"