Adicionar espaços antes de letras maiúsculas

193

Dada a sequência "ThisStringHasNoSpacesButItDoesHaveCapitals", qual é a melhor maneira de adicionar espaços antes das letras maiúsculas. Portanto, a sequência final seria "Esta sequência não possui espaços, mas possui letras maiúsculas"

Aqui está minha tentativa com um RegEx

System.Text.RegularExpressions.Regex.Replace(value, "[A-Z]", " $0")
Prumo
fonte
2
Você tem uma reclamação específica sobre a abordagem adotada? Isso pode nos ajudar a melhorar seu método.
Blair Conrad
Se o regex funcionar, eu continuaria com isso. Regex é otimizado para manipulação de strings.
22630 Michael Meadows #
Só estou curioso para saber se existe uma abordagem melhor ou talvez até integrada. Eu ficaria curioso para ver outras abordagens com outros idiomas.
Bob
2
Seu código simplesmente não funcionou porque a string modificada é o valor de retorno da função 'Replace'. Com esta linha de código: 'System.Text.RegularExpressions.Regex.Replace (value, "[AZ]", "$ 0"). Trim ();' funcionaria perfeitamente. (Apenas comentando porque eu tropeçou este post e ninguém realmente viu, o que estava errado com o seu código.)
Mattu475
Regex.Replace ("ThisStringHasNoSpacesButItDoesHaveCapitals", @ "\ B [AZ]", m => "" + m);
adil Saquib

Respostas:

203

As expressões regulares funcionarão bem (eu até votei na resposta de Martin Browns), mas são caras (e pessoalmente acho qualquer padrão mais longo que alguns caracteres proibitivamente obtusos)

Esta função

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

Será feito 100.000 vezes em 2.968.750 ticks, o regex terá 25.000.000 ticks (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" geralmente é o comprometimento de requisitos concorrentes.

Espero que isto ajude :)

Atualização
Já faz um bom tempo desde que olhei para isso, e acabei de perceber que os tempos não foram atualizados desde que o código mudou (apenas mudou um pouco).

Em uma sequência com 'Abbbbbbbbb' repetida 100 vezes (ou seja, 1.000 bytes), uma execução de 100.000 conversões assume a função codificada manualmente 4.517.177 ticks, e o Regex abaixo leva 59.435.719 fazendo a função codificada manualmente ser executada em 7,6% do tempo necessário. Regex.

Atualização 2 Levará em consideração os acrônimos? Será agora! A lógica da declaraçã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 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();
}
Preocupação binária
fonte
8
if (char.IsUpper (text [i]) && text [i - 1]! = '') Se você executar novamente o código acima, ele continuará adicionando espaços, isso interromperá a adição de espaços, se houver um espaço antes da capital carta.
Paul Talbot
Não tenho certeza, então pensei em perguntar: esse método manipula acrônimos, conforme descrito na resposta de Martin Brown, "DriveIsSCSICompatible" seria idealmente "Drive Drive SCSI Compatible"?
Coops
Isso o transformou em um caractere, substituindo o conteúdo da sua instrução for pelas instruções if atualizadas recentemente, posso estar fazendo algo errado?
Coops
1
Adicionar um cheque para char.IsLetter (texto [i + 1]) ajuda com acrônimos com caracteres e dígitos especiais (por exemplo, ABC_DEF não será dividido como AB C_DEF).
HeXanon
1
Não sei se a parte dos acrônimos está correta quando está desativada. Acabei de executar um teste "ASentenceABC" se expande para "ASentence AB C". Deve ser "uma sentença AB C"
Tim Rutter
149

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 também a letra minúscula que a precede e insira o espaço no meio:

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

Editar 1:

Se você usá- @"(\p{Ll})(\p{Lu})"lo, também selecionará caracteres acentuados.

Edição 2:

Se suas seqüências de caracteres podem conter siglas, convém usar isso:

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

Assim, "DriveIsSCSICompatible" se torna "Drive Is SCSI Compatible"

Martin Brown
fonte
3
Você não poderia apenas manter o resultado do RegEx e Trim () originais?
PandaWood
3
@PandaWood você poderia, mas isso exigiria outra alocação de memória e cópia de string. Dito isto, se o desempenho é uma preocupação, um Regex provavelmente não é o melhor caminho a seguir.
Martin Brown
Você também pode usar "([^A-Z\\s])([A-Z])", mesmo com siglas?
Ruben9922 9/01
82

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

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

Sei que é antigo, mas é uma extensão que 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()

Rob Hardy
fonte
Eu gosto da ideia disso como um método de extensão, se você adicionar, TrimStart(' ')ele removerá o espaço principal.
user1069816
1
Obrigado @ user1069816. Alterei o ramal para usar a sobrecarga, SelectManyque inclui um índice, dessa forma evita a primeira letra e o potencial desnecessário de sobrecarga de uma chamada adicional para TrimStart(' '). Roubar.
Rob Hardy
8

Bem-vindo ao Unicode

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

Eu forneço quatro soluções, variando do pior ao melhor. Somente o melhor tem sempre razão. Os outros têm problemas. Aqui está um teste para mostrar o que funciona e o que não funciona e onde. Usei sublinhados para que você possa ver onde os espaços foram colocados e marquei 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 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, quase todo mundo aqui selecionou o primeiro caminho, o marcado "Pior". Alguns selecionaram a segunda maneira, marcada com "OK". Mas ninguém antes de mim mostrou como fazer a abordagem "Melhor" ou "Melhor".

Aqui está o programa de teste com seus quatro métodos:

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

Quando você conseguir a mesma pontuação como "Melhor" neste conjunto de dados, saberá que fez isso corretamente. Até então, você não tinha. Ninguém aqui se saiu 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 destaque do StackOverflow é miseravelmente estúpido novamente. Eles estão fazendo o mesmo velho e coxo que (a maioria, mas não todos), do restante das abordagens pobres mencionadas aqui. Não é muito tempo para colocar o ASCII em repouso? Não faz mais sentido, e fingir que é tudo que você tem é simplesmente errado. Isso cria um código incorreto.

tchrist
fonte
sua resposta 'Melhor' parece a mais próxima até agora, mas não parece ser a pontuação inicial ou outras letras maiúsculas e minúsculas. Isso parece funcionar melhor para mim (em java): replaceAll ("(? <= [^^ \\ p {javaUpperCase}])) (? = [\\ p {javaUpperCase}])", "");
Randyaa
Hmm. Não sei se os algarismos romanos devem realmente contar como maiúsculas neste exemplo. O exemplo do modificador de letra definitivamente não deve ser contado. Se você for ao McDonalds.com, verá que está escrito sem espaço.
Martin Brown
Note-se também que você nunca conseguirá que isso seja perfeito. Por exemplo, eu gostaria de ver um exemplo que classifique "AlexandervonHumboldt", que deve terminar como "Alexander von Humboldt". Existem, obviamente, idiomas que não têm a distinção entre maiúsculas e minúsculas.
Martin Brown
8

Decidi criar um método de extensão simples, baseado no código do Binary Worrier, que manipulará os acrônimos corretamente e é repetível (não manipulará palavras 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 em que essa função passa. Adicionei a maioria dos casos sugeridos por tchrist a esta lista. Os três dos quais não passa (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());
Kevin Stricker
fonte
Semelhante a outra solução postada aqui, ele falha com a sequência "RegularOTs". Ele retorna "Regular O Ts"
Patee Gutee
4

Preocupante binário, usei o código sugerido e é bastante bom, tenho apenas uma pequena adição 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 NOX', o que está obviamente errado, pois deveria ser 'Average NOX'.

Infelizmente, ainda existe o erro de que, se você tiver o texto 'FromAStart', obterá o 'From AStart'.

Alguma idéia de consertar isso?

Richard Priddy
fonte
Talvez algo assim funcione: char.IsUpper (texto [i]) && (char.IsLower (texto [i - 1]) || (char.IsLower (texto [i + 1]))
Martin Brown
1
Este é o correto: if (char.IsUpper(text[i]) && !(char.IsUpper(text[i - 1]) && char.IsUpper(text[i + 1])))Resultado do teste: "Desde o início", "Desde o início", "Desde o início", mas você precisa i < text.Length - 1na condição do loop for para ignorar o último caractere e evitar a exceção fora do intervalo.
CallMeLaNN
Oh, da mesma forma. ! (a && b) e (! a ||! b) porque inferior =! superior.
CallMeLaNN
3

Aqui está o meu:

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
fonte
Isso deveria ser c #? Em caso afirmativo, em que namespace está List? Você quer dizer ArrayList ou List <string>?
Martin Brown
A lista <string> ficaria bem. Me desculpe por isso.
Cory Foy #
@ Martin Ele sempre teve a sintaxe correta, estava apenas escondida em um <pre><code>code</code></pre>bloco em vez da sintaxe Markdown. Não há necessidade de voto negativo (se fosse você).
George Stocker 28/02
3

Certifique-se de que você não está colocando os espaços no início da cadeia, mas você está colocando-os entre as capitais consecutivos. Algumas das respostas aqui não tratam de um ou de ambos os pontos. Existem outras maneiras além da regex, mas se você preferir usar isso, tente o seguinte:

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

O \Bé negado \b, portanto representa um limite que não é da palavra. Isso significa que o padrão corresponde a "Y" em XYzabc, mas não em Yzabcou X Yzabc. Como um pequeno bônus, você pode usar isso em uma string com espaços e ela não os duplicará.

Justin Morgan
fonte
3

Este 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, "([A-Z])([a-z]*)", " $1$2");

Observe o espaço em frente se "$ 1 $ 2", é isso que fará com que seja feito.

Este é o resultado:

"This Is A String Without Spaces"
Matthias Thomann
fonte
1
Se você quiser que os números também sejam separados, use esse padrão de regex:"([A-Z0-9])([a-z]*)"
Matthias Thomann
2

O que você tem funciona perfeitamente. Lembre-se de reatribuir valueao valor de retorno dessa função.

value = System.Text.RegularExpressions.Regex.Replace(value, "[A-Z]", " $0");
Bill the Lizard
fonte
2

Aqui está como você pode fazer isso no 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
fonte
2

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

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
fonte
Eu gosto desta solução. É curto e rápido. No entanto, semelhante a outras soluções, ele falha com a sequência "RegularOTs". Todas as soluções que tentei aqui retornam "Regular O Ts"
Patee Gutee
@PateeGutee o OP queria espaço antes capitols, ele não mencionou abreviaturas, temos uma correção para que no bacalhau de produção
johnny 5
Você pode mostrar a correção? Eu tenho seqüências de caracteres como esta em meus dados e está me dando resultado incorreto. Obrigado.
Patee Gutee
@PateeGutee Desculpe, eu interpretei mal o que você queria. Pluralização é um problemas diferentes, `RegularOTs' o que você está esperando para acontecer 'OTs regulares' ou 'Regular OT s'
johnny 5
1
@PateeGutee Eu atualizei a minha resposta para você, eu acredito que deve funcionar
johnny 5
1
replaceAll("(?<=[^^\\p{Uppercase}])(?=[\\p{Uppercase}])"," ");
Randyaa
fonte
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();
    }
cirilo
fonte
1

Em Ruby, via Regexp:

"FooBarBaz".gsub(/(?!^)(?=[A-Z])/, ' ') # => "Foo Bar Baz"
Artem
fonte
1
OPA, desculpe. Eu perdi que de C # questão espec�ico e postado aqui resposta Rubi :(
Artem
1

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

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

A questão é um pouco antiga, mas hoje em dia existe uma boa biblioteca no Nuget que faz exatamente isso, além de muitas outras conversões em texto legível por humanos.

Confira o 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"
Jonas Pegerfalk
fonte
Apenas tentei e o primeiro link agora está quebrado. O NuGet funciona, mas o pacote não compila na minha solução. Uma boa ideia, se funcionou.
PhilW
1

Parece uma boa oportunidade para 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();
Dave Cousineau
fonte
0

Além da resposta de Martin Brown, também tive um problema com os números. Por exemplo: "Local2" ou "Jan22" deve ser "Local 2" e "22 de janeiro", respectivamente.

Aqui está 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ão regular baseado em Java (mas funciona para a maioria dos regex .net)

Analisador Baseado em Script de Ação

A regex acima não funcionará no site do script de ação, a menos que você substitua todos por \p{Ll}with [a-z], the \p{Lu}with [A-Z]e \p{Nd}with [0-9].

Daryl
fonte
0

Aqui está minha solução, com base na sugestão e construção dos Binários Preocupantes nos comentários de Richard Priddys, mas também levando em consideração que pode haver espaço em branco na cadeia fornecida, para que ele não adicione 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();
    }
Yetiish
fonte
0

Para quem procura uma função C ++ que responda a essa mesma pergunta, você pode usar o seguinte. Isso é modelado após a resposta dada pelo @Binary Worrier. Este método apenas preserva 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 strings de teste que usei para esta função e os resultados são:

  • "olá Mundo" -> "olá Mundo"
  • "HelloWorld" -> "Hello World"
  • "HelloABCWorld" -> "Olá ABC World"
  • "HelloWorldABC" -> "Olá, mundo ABC"
  • "ABCHelloWorld" -> "ABC Hello World"
  • "ABC OLÁ MUNDO" -> "ABC OLÁ MUNDO"
  • "ABCHELLOWORLD" -> "ABCHELLOWORLD"
  • "A" -> "A"
lbrendanl
fonte
0

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

Veja também a demonstração 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);
    }
}

Saída esperada:

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

Atualização: Aqui está uma variação que também manipula acrônimos (sequências de letras maiúsculas).

Veja também a demo regex101.com e a 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);
    }
}

Saída esperada:

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

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 concisos, mas ele também manipula acrônimos e palavras de uma letra)

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

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.)
CrazyTim
fonte
Este saídas "Essa frase tem NoSpaces Mas ItDoes Tem Capitais"
Andy Robinson
Olá @AndyRobinson, obrigado. Mudei para usar várias substituições Regex. Não tenho certeza se existe uma maneira mais concisa, mas funciona agora.
CrazyTim
0

Todas as respostas anteriores pareciam muito complicadas.

Eu tinha uma string que tinha uma mistura de maiúsculas e _ usada, string.Replace () para criar o _, "" e usei o seguinte para adicionar um espaço nas 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.
        }
    }
}
st3_121
fonte
0

Inspirado pela resposta do binário, preocupei-me com isso.

Aqui está o resultado:

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

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

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

João Sequeira
fonte
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
fonte
0

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 nos testes:

string test1 = "RegularOTs";
string test2 = "ThisStringHasNoSpacesASCIIButItDoesHaveCapitalsLINQ";
string test3 = "ThisStringHasNoSpacesButItDoesHaveCapitals";
Serj Sagan
fonte
os aceitei resposta lida com o caso em que o valor é nulo
Chris F Carroll
Isso adiciona um espaço extra na frente da saída, ou seja, HireDate => "Data da contratação". Precisa de um final.TrimStart ou algo assim. Eu acho que é o que uma das outras respostas está apontando abaixo, mas devido à reordenação, não tenho certeza se ele estava falando com você, já que a resposta dele é baseada em RegEx.
22615 B_levitt 06/02
Boa captura ... deveria ter adicionado um marcador de início e fim aos meus testes ... corrigido agora.
Serj Sagan
Semelhante a outra solução postada aqui, ele falha com a sequência "RegularOTs". Ele retorna "Regular O Ts"
Patee Gutee
Obrigado por apresentar os plurais das abreviações, eu atualizei para trabalhar para isso também.
Serj Sagan
0

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

Além da solicitação, essa implementação salva corretamente os espaços à esquerda, à direita, à direita e os acrônimos, por exemplo,

" SpacedWord " => " Spaced Word ",  

"Inner Space" => "Inner Space",  

"SomeACRONYM" => "Some ACRONYM".
Artru
fonte
0

Uma maneira simples de adicionar espaços após letras minúsculas, maiúsculas ou dígitos.

    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
fonte
1
As respostas somente de código são desencorajadas. Clique em editar e adicione algumas palavras resumindo como o seu código aborda a pergunta ou talvez explique como a sua resposta difere da resposta / respostas anteriores. Da avaliação
Nick