Adicione espaços antes das letras maiúsculas

Dada a string “ThisStringHasNoSpacesButItDoesHaveCapitals”, qual é a melhor maneira de adicionar espaços antes das letras maiúsculas. Então a string final seria “Esta string não tem espaços, mas tem maiúsculas”

Aqui está a minha tentativa com um RegEx

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

Os regexes funcionarão bem (eu até votei na resposta de Martin Brown), mas eles são caros (e pessoalmente eu acho qualquer padrão mais longo que um par de caracteres proibitivamente obtusos)

Esta function

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

Vai fazer isso 100.000 vezes em 2.968.750 carrapatos, o regex vai levar 25.000.000 carrapatos (e isso com o regex compilado).

É melhor, para um determinado valor de melhor (ou seja, mais rápido), no entanto, é mais código para manter. "Melhor" é frequentemente um compromisso de requisitos concorrentes.

Espero que isto ajude 🙂

Atualizar
Já faz muito tempo desde que eu olhei para isso, e percebi que os tempos não foram atualizados desde que o código mudou (ele só mudou um pouco).

Em uma string com 'Abbbbbbbbb' repetida 100 vezes (ie 1.000 bytes), uma corrida de 100.000 conversões recebe a function codificada manualmente 4.517.177 ticks, e o Regex abaixo leva 59.435.719 fazendo com que a function Hand coded seja executada em 7,6% do tempo que leva Regex.

Atualização 2 Levará em conta os acrônimos? Agora vai! A lógica da instrução if é bastante obscura, como você pode ver expandindo-a para isso ...

 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ão ajuda em nada!

Aqui está o método simples original que não se preocupa com os 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(); } 

Sua solução tem um problema, pois coloca um espaço antes da primeira letra T para que você obtenha

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

Para contornar isso, procure a letra minúscula que a precede e insira o espaço no meio:

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

Editar 1:

Se você usar @"(\p{Ll})(\p{Lu})" ele também selecionará caracteres acentuados.

Editar 2:

Se suas strings puderem conter siglas, você pode querer usar isto:

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

Então, “DriveIsSCSICompatible” torna-se “Drive Is SCSI Compatible”

Não testei o desempenho, mas aqui em uma linha com o linq:

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

Eu sei que este é um antigo, mas esta é uma extensão que eu uso quando preciso fazer isso:

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

Isso permitirá que você use MyCasedString.ToSentence()

Bem vindo ao Unicode

Todas essas soluções são essencialmente erradas para o texto moderno. Você precisa usar algo que entenda o caso. Como Bob pediu outros idiomas, eu dou um par para o Perl.

Eu forneço quatro soluções, variando do pior para o melhor. Apenas o melhor está sempre certo. Os outros têm problemas. Aqui está um teste para mostrar o que funciona e o que não funciona e onde. Eu usei sublinhados para que você possa ver onde os espaços foram colocados, e eu assinalei como errado qualquer coisa que esteja, bem, errada.

 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, quase todo mundo aqui selecionou o primeiro caminho, o marcado “Pior”. Alguns selecionaram o segundo caminho, marcado como “OK”. Mas ninguém antes de mim mostrou como fazer a abordagem “Melhor” ou “Melhor”.

Aqui está o programa de teste com seus quatro methods:

 #!/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; } 

Quando conseguir pontuar o mesmo que o "Melhor" neste dataset, você saberá que fez isso corretamente. Até então, você não tem. Ninguém mais aqui fez melhor do que "Ok", e a maioria fez "pior". Estou ansioso para ver alguém postar o código correto.

Percebo que o código de realce do StackOverflow está miseravelmente mais embaraçado. Eles estão fazendo a mesma coisa de ruim que (a maioria, mas não todos) das outras abordagens pobres mencionadas aqui fizeram. Não é tempo de passar o ASCII para descansar? Isso não faz mais sentido, e fingir que tudo que você tem é simplesmente errado. Faz para o código ruim.

Eu me propus a fazer um método de extensão simples baseado no código do Worrier Binário, que vai lidar com acrônimos corretamente, e é repetível (não mangle palavras já espaçadas). Aqui está o meu 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(); } 

Aqui estão os casos de teste de unidade que esta function passa. Eu adicionei a maioria dos casos sugeridos pelo tchrist a esta lista. Os três que não passam (dois são apenas algarismos romanos) são 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, eu usei o seu código sugerido, e é bastante bom, eu tenho apenas um pequeno acréscimo a ele:

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

Eu adicionei uma condição !char.IsUpper(text[i - 1]) . Isso corrigiu um bug que faria com que algo como 'AverageNOX' fosse transformado em 'Average NO X', o que obviamente está errado, já que deveria ser 'Average NOX'.

Infelizmente isso ainda tem o bug que se você tiver o texto 'FromAStart', você obteria 'From AStart' para fora.

Alguma idéia de consertar isso?

Aqui está o meu:

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

Certifique-se de não colocar espaços no começo da string, mas você os coloca entre maiúsculas consecutivas. Algumas das respostas aqui não abordam um ou ambos os pontos. Existem outras formas de regex, mas se você preferir usar isso, tente isto:

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

O \B é um \b negado, portanto representa um limite não de palavras. Isso significa que o padrão corresponde a “Y” em XYzabc , mas não em Yzabc ou X Yzabc . Como um pequeno bônus, você pode usar isso em uma string com espaços e não irá dobrá-los.

O que você tem funciona perfeitamente. Apenas lembre-se de reatribuir value ao value de retorno desta function.

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

Aqui está como você poderia fazer isso em 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 

Esse Regex coloca um caractere de espaço na frente de cada letra maiúscula:

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

Mente o espaço na frente se “$ 1 $ 2”, é isso que vai fazê-lo.

Este é o resultado:

 "This Is A String Without Spaces" 

Inspirado em @MartinBrown, Duas Linhas de Regex Simples, que resolverá seu nome, incluindo Acrônimos em qualquer lugar da string.

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

Em Ruby, via Regexp:

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

Eu levei Kevin Strikers excelente solução e converti para VB. Desde que eu estou bloqueado no .NET 3.5, eu também tive que escrever IsNullOrWhiteSpace. Isso passa todos os seus testes.

  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 

Além da Resposta de Martin Brown, também tive um problema com números. Por exemplo: “Location2” ou “Jan22” deve ser “Location 2” e “Jan 22”, respectivamente.

Aqui está a minha expressão regular para fazer isso, usando a resposta de Martin Brown:

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

Aqui estão alguns ótimos sites para descobrir o que cada parte significa também:

Analisador de Expressões Regulares Baseado em Java (mas funciona para a maioria dos regex de .net)

Analisador Baseado em Script de Ação

O regex acima não funcionará no site do script de ação, a menos que você substitua todos os \p{Ll} por [az] , o \p{Lu} com [AZ] e \p{Nd} com [0-9] .

Aqui está a minha solução, baseada na sugestão de Binary Worriers e na construção dos comentários de Richard Priddys, mas também levando em conta que o espaço em branco pode existir na string fornecida, por isso não irá adicionar espaço em branco ao lado do espaço em branco 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 quem está procurando uma function C ++ respondendo a mesma pergunta, você pode usar o seguinte. Isso é modelado após a resposta dada pelo @Binary Worrier. Este método apenas preserva os acrônimos automaticamente.

 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; 

As cadeias de testes que usei para essa function e os resultados são:

  • "helloWorld" -> "olá mundo"
  • "HelloWorld" -> "Hello World"
  • "HelloABCWorld" -> "Olá ABC World"
  • "HelloWorldABC" -> "Hello World ABC"
  • "ABCHelloWorld" -> "ABC Hello World"
  • "ABC HELLO WORLD" -> "ABC HELLO WORLD"
  • "ABCHELLOWORLD" -> "ABCHELLOWORLD"
  • "A" -> "A"

Uma solução C # para uma cadeia de input que consiste apenas em caracteres ASCII. A regex incorpora lookbehind negativo para ignorar uma letra maiúscula que aparece no início da string. Usa Regex.Replace () para retornar a string desejada.

Veja também a demonstração do 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 = "(?< !^)([AZ])"; var rgx = new Regex(pattern); var result = rgx.Replace(text, " $1"); Console.WriteLine("Input: [{0}]\nOutput: [{1}]", text, result); } } 

Saída esperada:

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

Update: Aqui está uma variação que também irá lidar com siglas (sequências de letras maiúsculas).

Veja também demo regex101.com e 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 = "(?< =[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); } } 

Saída esperada:

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

A questão é um pouco antiga, mas hoje em dia há uma boa biblioteca no Nuget que faz exatamente isso, assim como muitas outras conversões em textos legíveis por humanos.

Confira Humanizer no GitHub ou Nuget.

Exemplo

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

Aqui está uma solução mais completa que não coloca espaços na frente das palavras:

Nota: Eu usei vários Regexs (não conciso, mas também irá lidar com siglas e palavras de uma única 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 

Em :

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

Fora :

 "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 as respostas anteriores pareciam muito complicadas.

Eu tinha uma string que tinha uma mistura de maiúsculas e _ assim usada, string. Substitua () para fazer o _, “” e usei o seguinte para adicionar um espaço às letras maiú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. } } } 

Inspirado pela resposta da Binary Worrier, eu dei um giro nisso.

Aqui está o 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(); } 

Testei usando o cronômetro executando 10000000 iterações e vários comprimentos de string e combinações.

Em média, 50% (talvez um pouco mais) mais rápido que a resposta de Worrier Binary.

Parece uma boa oportunidade para o Aggregate . Isso foi projetado para ser legível, não necessariamente 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

Este inclui acrônimos e plurais de acrônimos e é um pouco mais rápido que a resposta aceita:

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

Passa estes testes:

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

Uma implementação com fold , também conhecida como 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".