Agregar espacios antes de mayúsculas

Dada la cadena “ThisStringHasNoSpacesButItDoesHaveCapitals”, ¿cuál es la mejor manera de agregar espacios antes de las letras mayúsculas? Entonces, la cadena final sería “Esta cadena no tiene espacios pero tiene capiteles”

Aquí está mi bash con un RegEx

System.Text.RegularExpressions.Regex.Replace(value, "[AZ]", " $0") 

Las expresiones regulares funcionarán bien (incluso voté la respuesta de Martin Browns), pero son caras (y personalmente encuentro cualquier patrón más largo que un par de caracteres prohibitivamente obtusos)

Esta función

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

Lo hará 100.000 veces en 2,968,750 tics, la expresión regular tomará 25,000,000 tics (y eso con la expresión regular comstackda).

Es mejor, para un valor dado de mejor (es decir, más rápido), sin embargo, es más código para mantener. "Mejor" es a menudo el compromiso de los requisitos de la competencia.

Espero que esto ayude 🙂

Actualizar
Pasó mucho tiempo desde que miré esto, y me di cuenta de que los tiempos no se han actualizado desde que el código cambió (solo cambió un poco).

En una cadena con 'Abbbbbbbbb' repetida 100 veces (es decir, 1,000 bytes), una ejecución de 100,000 conversiones toma la función codificada a mano 4,517,177 tics, y la Regex a continuación toma 59,435,719 haciendo que la función codificada a mano se ejecute en el 7.6% del tiempo que toma el Regex.

Actualización 2 ¿Tomará Acrónimos en cuenta? ¡Lo hará ahora! La lógica de la statement if es bastante oscura, como puedes ver al expandirla a esto ...

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

... no ayuda en absoluto!

Aquí está el método simple original que no se preocupa por los acrónimos

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

Su solución tiene un problema ya que pone un espacio antes de la primera letra T para que obtenga

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

Para solucionar este problema, busque la letra minúscula que lo precede y luego inserte el espacio en el medio:

 newValue = Regex.Replace(value, "([az])([AZ])", "$1 $2"); 

Editar 1:

Si usa @"(\p{Ll})(\p{Lu})" también captará caracteres acentuados.

Editar 2:

Si sus cadenas pueden contener acrónimos, puede usar esto:

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

Entonces “DriveIsSCSICompatible” se convierte en “Drive is SCSI Compatible”

No evaluó el rendimiento, pero aquí en una línea con linq:

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

Sé que esta es una antigua, pero esta es una extensión que uso cuando necesito hacer esto:

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

Esto le permitirá usar MyCasedString.ToSentence()

Bienvenido a Unicode

Todas estas soluciones son esencialmente incorrectas para el texto moderno. Necesita usar algo que comprenda el caso. Como Bob pidió otros idiomas, le daré un par a Perl.

Proporciono cuatro soluciones, que van desde la peor hasta la mejor. Solo el mejor siempre tiene la razón. Los otros tienen problemas. Aquí hay una prueba para mostrarle qué funciona y qué no, y dónde. He usado guiones bajos para que pueda ver dónde se han puesto los espacios, y he marcado como incorrecto todo lo que está, bueno, mal.

 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 

Por cierto, casi todos aquí han seleccionado la primera manera, la marcada como “Peor”. Algunos han seleccionado la segunda manera, marcada “OK”. Pero nadie más antes que yo le ha mostrado cómo hacer el enfoque “Mejor” o “Mejor”.

Aquí está el progtwig de prueba con sus cuatro métodos:

 #!/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/(?<=[az])(?=[AZ])/_/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; } 

Cuando puede obtener el mismo puntaje que el "Mejor" en este conjunto de datos, sabrá que lo ha hecho correctamente. Hasta entonces, no lo has hecho. A nadie más le ha ido mejor que a "Ok", y la mayoría lo ha hecho "Peor". Espero ver a alguien publicando el código ℂ♯ correcto.

Noto que el código de resaltado de StackOverflow es miserablemente stoopid nuevamente. Están haciendo la misma vieja cojera que (la mayoría, pero no todas) del rest de los enfoques pobres que aquí se mencionan. ¿Ya es hora de dejar descansar a ASCII? Ya no tiene sentido, y pretender que es todo lo que tienes es simplemente incorrecto. Es un mal código.

Me propuse hacer un método de extensión simple basado en el código de Binary Worrier que manejará acrónimos correctamente, y es repetible (no destrozará palabras ya espaciadas). Aquí está mi resultado.

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

Aquí están los casos de prueba de unidad que esta función pasa. Agregué la mayoría de los casos sugeridos de Tchrist a esta lista. Los tres de los que no pasa (dos son solo números romanos) están comentados:

 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, he usado su código sugerido, y es bastante bueno, solo tengo una pequeña adición:

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

He agregado una condición !char.IsUpper(text[i - 1]) . Esto corrigió un error que causaría que algo como 'AverageNOX' se convirtiera en 'Average NO X', lo cual es obviamente incorrecto, ya que debería decir 'Average NOX'.

Lamentablemente, esto todavía tiene el error de que si tienes el texto 'FromAStart', obtendrías 'From AStart'.

¿Alguna idea sobre arreglar esto?

Aquí está el mío:

 private string SplitCamelCase(string s) { Regex upperCaseRegex = new Regex(@"[AZ]{1}[az]*"); MatchCollection matches = upperCaseRegex.Matches(s); List words = new List(); foreach (Match match in matches) { words.Add(match.Value); } return String.Join(" ", words.ToArray()); } 

Asegúrese de no poner espacios al principio de la cadena, pero los está colocando entre mayúsculas consecutivas. Algunas de las respuestas aquí no abordan uno o ambos puntos. Hay otras formas que no son expresiones regulares, pero si prefiere usarlas, intente esto:

 Regex.Replace(value, @"\B[AZ]", " $0") 

El \B es un negado \b , por lo que representa un límite no de palabra. Significa que el patrón coincide con “Y” en XYzabc , pero no en Yzabc o X Yzabc . Como una pequeña ventaja, puede usar esto en una cadena con espacios en ella y no los duplicará.

Lo que tienes funciona a la perfección. Solo recuerde reasignar el value al value de retorno de esta función.

 value = System.Text.RegularExpressions.Regex.Replace(value, "[AZ]", " $0"); 

Aquí es cómo puedes hacerlo 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 

Esta Regex coloca un carácter de espacio delante de cada letra mayúscula:

 using System.Text.RegularExpressions; const string myStringWithoutSpaces = "ThisIsAStringWithoutSpaces"; var myStringWithSpaces = Regex.Replace(myStringWithoutSpaces, "([AZ])([az]*)", " $1$2"); 

Tenga en cuenta el espacio en el frente si “$ 1 $ 2”, esto es lo que hará que se haga.

Este es el resultado:

 "This Is A String Without Spaces" 

Inspirado en @MartinBrown, Two Lines of Simple Regex, que resolverá su nombre, incluyendo Acyronyms en cualquier lugar de la cadena.

 public string ResolveName(string name) { var tmpDisplay = Regex.Replace(name, "([^AZ ])([AZ])", "$1 $2"); return Regex.Replace(tmpDisplay, "([AZ]+)([AZ][^AZ$])", "$1 $2").Trim(); } 
 replaceAll("(?<=[^^\\p{Uppercase}])(?=[\\p{Uppercase}])"," "); 
 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(); } 

En Ruby, a través de Regexp:

 "FooBarBaz".gsub(/(?!^)(?=[AZ])/, ' ') # => "Foo Bar Baz" 

Tomé la excelente solución de Kevin Strikers y la convertí en VB. Como estoy en .NET 3.5, también tuve que escribir IsNullOrWhiteSpace. Esto pasa todas sus pruebas.

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

Además de la respuesta de Martin Brown, también tuve problemas con los números. Por ejemplo: “Location2”, o “Jan22” deberían ser “Location 2”, y “Jan 22” respectivamente.

Aquí está mi Expresión regular para hacer eso, usando la respuesta de Martin Brown:

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

Aquí hay un par de excelentes sitios para descubrir lo que significa cada parte también:

Analizador de expresión regular basado en Java (pero funciona para la mayoría de los regex de .net)

Analizador basado en script de acción

La expresión regular anterior no funcionará en el sitio de script de acción a menos que reemplace todo el \p{Ll} con [az] , el \p{Lu} con [AZ] y \p{Nd} con [0-9]

Esta es mi solución, basada en la sugerencia de Binary Worriers y basada en los comentarios de Richard Priddys, pero también teniendo en cuenta que puede existir espacio en blanco en la cadena proporcionada, por lo que no agregará espacio en blanco al lado del espacio en blanco existente.

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

Para cualquiera que esté buscando una función de C ++ respondiendo esta misma pregunta, puede usar lo siguiente. Esto se basa en la respuesta de @Binary Worrier. Este método solo conserva Acrónimos automáticamente.

 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; 

Las cadenas de prueba que utilicé para esta función, y los resultados son:

  • "helloWorld" -> "hola mundo"
  • "HelloWorld" -> "Hola mundo"
  • "HelloABCWorld" -> "Hola mundo ABC"
  • "HelloWorldABC" -> "Hola mundo ABC"
  • "ABCHelloWorld" -> "ABC Hello World"
  • "ABC HELLO WORLD" -> "ABC HELLO WORLD"
  • "ABCHELLOWORLD" -> "ABCHELLOWORLD"
  • "A" -> "A"

Una solución de C # para una cadena de entrada que consta únicamente de caracteres ASCII. La expresión regular incorpora lookbehind negativo para ignorar una letra mayúscula (mayúscula) que aparece al principio de la cadena. Utiliza Regex.Replace () para devolver la cadena deseada.

También vea la demo 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 = "(? 

Rendimiento esperado:

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

Actualización: Aquí hay una variación que también manejará acrónimos (secuencias de letras mayúsculas).

También vea la demostración de demo e ideone.com de regex101.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 = "(?<=[az])([AZ])"; // 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 = "(?<=[AZ])([AZ])(?=[az])"; var pattern = patternPart1 + "|" + patternPart2; var rgx = new Regex(pattern); var result = rgx.Replace(text, " $1$2"); Console.WriteLine("Input: [{0}]\nOutput: [{1}]", text, result); } } 

Rendimiento esperado:

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

La pregunta es un poco antigua, pero hoy en día hay una buena biblioteca en Nuget que hace exactamente esto y muchas otras conversiones en texto legible por humanos.

Echa un vistazo a Humanizer en GitHub o Nuget.

Ejemplo

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

Aquí hay una solución más completa que no pone espacios delante de las palabras:

Nota: He usado varios Regex (no es conciso, pero también manejará acrónimos y palabras de una sola letra)

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

En :

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

Fuera :

 "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.) 

Todas las respuestas anteriores parecían demasiado complicadas.

Tenía un hilo que tenía una mezcla de mayúsculas y _ así que lo usé, string.Replace () para hacer el _, “” y usé lo siguiente para agregar un espacio con las letras mayúsculas.

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

Inspirada en la respuesta de Binary Worrier, di un paso hacia esto.

Este es el resultado:

 ///  /// String Extension Method /// Adds white space to strings based on Upper Case Letters ///  ///  /// strIn => "HateJPMorgan" /// preserveAcronyms false => "Hate JP Morgan" /// preserveAcronyms true => "Hate JPMorgan" ///  /// to evaluate /// determines saving acronyms (Optional => false)  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(); } 

Hice la prueba usando el cronómetro ejecutando 10000000 iteraciones y varias longitudes de cadena y combinaciones.

En promedio, 50% (quizás un poco más) más rápido que la respuesta Binary Worrier.

Parece una buena oportunidad para Aggregate . Esto está diseñado para ser legible, no necesariamente especialmente rápido.

 someString .Aggregate( new StringBuilder(), (str, ch) => { if (char.IsUpper(ch) && str.Length > 0) str.Append(" "); str.Append(ch); return str; } ).ToString(); 
  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 

Éste incluye acrónimos y acrónimos plurales y es un poco más rápido que la respuesta aceptada:

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

Pasa estas pruebas:

 string test1 = "RegularOTs"; string test2 = "ThisStringHasNoSpacesASCIIButItDoesHaveCapitalsLINQ"; string test3 = "ThisStringHasNoSpacesButItDoesHaveCapitals"; 

An implementation with fold , also known as Aggregate :

  public static string SpaceCapitals(this string arg) => new string(arg.Aggregate(new List(), (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()); 

In addition to the request, this implementation correctly saves leading, inner, trailing spaces and acronyms, for example,

 " SpacedWord " => " Spaced Word ", "Inner Space" => "Inner Space", "SomeACRONYM" => "Some ACRONYM".