Agregar espacios antes de mayúsculas

193

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 mayúsculas"

Aquí está mi intento con un RegEx

System.Text.RegularExpressions.Regex.Replace(value, "[A-Z]", " $0")
Beto
fuente
2
¿Tiene una queja particular sobre el enfoque que ha tomado? Eso podría ayudarnos a mejorar su método.
Blair Conrad el
Si la expresión regular funciona, entonces me quedaría con eso. Regex está optimizado para la manipulación de cadenas.
Michael Meadows el
Tengo curiosidad por saber si hay un enfoque mejor o quizás incluso más integrado. Incluso tendría curiosidad por ver otros enfoques con otros idiomas.
Bob
2
Su código simplemente no funcionó porque la cadena modificada es el valor de retorno de la función 'Reemplazar'. Con esta línea de código: 'System.Text.RegularExpressions.Regex.Replace (value, "[AZ]", "$ 0"). Trim ();' Funcionaría perfectamente. (Solo comento porque me topé con esta publicación y nadie realmente vio lo que estaba mal con su código.)
Mattu475
Regex.Replace ("ThisStringHasNoSpacesButItDoesHaveCapitals", @ "\ B [AZ]", m => "" + m);
saquib adil

Respuestas:

203

Las expresiones regulares funcionarán bien (incluso voté por 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 ticks, la expresión regular tomará 25,000,000 ticks (y eso es con la expresión regular compilada).

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 un compromiso de requisitos competitivos.

Espero que esto ayude :)

Actualización
Hace mucho tiempo que no veo esto, y me di cuenta de que los tiempos no se han actualizado desde que cambió el código (solo cambió un poco).

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

Actualización 2 ¿Tendrá en cuenta las siglas? ¡Lo hará ahora! La lógica de la declaración if es bastante oscura, como puede ver expandiéndola 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();
}
revés Binario Worrier
fuente
8
if (char.IsUpper (text [i]) && text [i - 1]! = '') Si vuelve a ejecutar el código anterior, sigue agregando espacios, esto detendrá la adición de espacios si hay un espacio antes de la capital letra.
Paul Talbot
No estoy seguro de lo que pensaba que iba a preguntar, hace este método de mango acrónimos como se describe en la respuesta de Martin Brown "DriveIsSCSICompatible" lo ideal sería convertido en "Drive es compatible SCSI"
Las cooperativas
Eso lo convirtió en 1 carácter al reemplazar el contenido de su declaración for por las declaraciones if recientemente actualizadas, ¿puedo estar haciendo algo mal?
Coops
1
Agregar un cheque para char.IsLetter (texto [i + 1]) ayuda con acrónimos con caracteres y dígitos especiales (es decir, ABC_DEF no se dividirá como AB C_DEF).
HeXanon
1
No estoy seguro de que la parte de los acrónimos sea correcta cuando está APAGADA. Acabo de ejecutar una prueba "ASentenceABC" se expande a "ASentence AB C". Debería ser "A Sentence AB C"
Tim Rutter
149

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

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

Para evitar este aspecto, busque también la letra minúscula que lo precede y luego inserte el espacio en el medio:

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

Editar 1:

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

Edición 2:

Si sus cadenas pueden contener siglas, 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"

Martin Brown
fuente
3
¿No podría simplemente mantener el RegEx original y Trim () el resultado?
PandaWood
3
@PandaWood podría pero requeriría otra asignación de memoria y copia de cadena. Dicho esto, si el rendimiento es una preocupación, Regex probablemente no sea la mejor manera de hacerlo.
Martin Brown
¿Podría usar también "([^A-Z\\s])([A-Z])", incluso con siglas?
Ruben9922
82

No probé 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(' ');
EtienneT
fuente
18

Sé que esta es 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 te permitirá usar MyCasedString.ToSentence()

Rob Hardy
fuente
Me gusta la idea de esto como un método de extensión, si lo agrega TrimStart(' '), eliminará el espacio inicial.
user1069816
1
Gracias @ user1069816. He cambiado la extensión para usar la sobrecarga, SelectManyque incluye un índice, de esta manera evita la primera letra y la sobrecarga potencial innecesaria de una llamada adicional a TrimStart(' '). Robar.
Rob Hardy
8

Bienvenido a Unicode

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

Proporciono cuatro soluciones, que van de peor a 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 colocado los espacios, y he marcado como incorrecto todo lo que es, bueno, incorrecto.

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

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

Aquí está el programa 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              ,
    $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;
}

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

Noté que el código de resaltado de StackOverflow es miserablemente estúpido nuevamente. Están haciendo todo lo mismo cojo que (la mayoría, pero no todos) del resto de los malos enfoques mencionados aquí han hecho. ¿No es hora de dejar de descansar ASCII? Ya no tiene sentido, y pretender que todo lo que tienes es simplemente incorrecto. Es un mal código.

tchrist
fuente
su "Mejor" respuesta parece la más cercana hasta ahora, pero no parece que explique la puntuación inicial u otras letras minúsculas iniciales. Esto parece funcionar mejor para mí (en Java): replaceAll ("(? <= [^^ \\ p {javaUpperCase}]) (? = [\\ p {javaUpperCase}])", "");
Randyaa
Hmm No estoy seguro de que los números romanos realmente cuenten como mayúsculas en este ejemplo. El ejemplo del modificador de letra definitivamente no debe contarse. Si va a McDonalds.com verá que está escrito sin espacios.
Martin Brown
También debe tenerse en cuenta que nunca conseguirá que esto sea perfecto. Por ejemplo, me gustaría ver un ejemplo que resuelva "AlexandervonHumboldt", que debería terminar como "Alexander von Humboldt". Luego, por supuesto, hay idiomas que no tienen el destino de mayúsculas y minúsculas.
Martin Brown
8

Me propuse hacer un método de extensión simple basado en el código de Binary Worrier que maneje los acrónimos correctamente y sea repetible (no alterará las 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 pasa esta función. 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) se comentan:

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
fuente
Similar a otra solución publicada aquí, falla con la cadena "RegularOTs". Devuelve "Regular O Ts"
Patee Gutee
4

Binario 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 solucionó un error que causaba que algo como 'AverageNOX' se convirtiera en 'Average NO X', lo que obviamente es incorrecto, ya que debería leer 'Average NOX'.

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

¿Alguna idea sobre arreglar esto?

Richard Priddy
fuente
Quizás algo como esto funcione: char.IsUpper (text [i]) && (char.IsLower (text [i - 1]) || (char.IsLower (text [i + 1]))
Martin Brown
1
Este es el correcto: if (char.IsUpper(text[i]) && !(char.IsUpper(text[i - 1]) && char.IsUpper(text[i + 1])))Resultado de la prueba: "Desde el inicio", "Desde el inicio", "Desde el inicio", pero necesita i < text.Length - 1en la condición de bucle for ignorar el último carácter y evitar una excepción fuera de rango.
CallMeLaNN
Oh, es lo mismo. ! (a && b) y (! a ||! b) porque lower =! upper.
CallMeLaNN
3

Aquí está el mío:

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
fuente
¿Se supone que eso es C #? Si es así, ¿en qué espacio de nombres está List? ¿Te refieres a ArrayList o List <string>?
Martin Brown
La lista <cadena> estaría bien. Lo siento por eso.
Cory Foy el
@ Martin Siempre tuvo la sintaxis correcta, simplemente estaba oculta en un <pre><code>code</code></pre>bloque en lugar de la sintaxis Markdown. No es necesario que lo rebajes (si fueras tú).
George Stocker
3

Asegúrese de que no están poniendo espacios al principio de la cadena, pero que están poniéndolos entre capitales consecutivos. Algunas de las respuestas aquí no abordan uno o ambos de esos puntos. Hay otras formas además de expresiones regulares, pero si prefiere usar eso, intente esto:

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

El \Bes un negado \b, por lo que representa un límite sin palabras. Significa que el patrón coincide con "Y" en XYzabc, pero no en Yzabco X Yzabc. Como un pequeño bono, puede usar esto en una cadena con espacios y no los duplicará.

Justin Morgan
fuente
3

Esta expresión regular 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, "([A-Z])([a-z]*)", " $1$2");

Tenga en cuenta el espacio en el frente si "$ 1 $ 2", esto es lo que lo hará.

Este es el resultado:

"This Is A String Without Spaces"
Matthias Thomann
fuente
1
Si desea que los números también se separen, use este patrón de expresiones regulares en su lugar:"([A-Z0-9])([a-z]*)"
Matthias Thomann
2

Lo que tienes funciona perfectamente. Solo recuerde reasignar valueel valor de retorno de esta función.

value = System.Text.RegularExpressions.Regex.Replace(value, "[A-Z]", " $0");
Bill el lagarto
fuente
2

Así es como 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
KCITGuy
fuente
2

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

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
fuente
Me gusta esta solución Es corto y rápido. Sin embargo, similar a otras soluciones, falla con la cadena "RegularOTs". Cada solución que probé aquí devuelve "Regular O Ts"
Patee Gutee
@PateeGutee, el OP quería espacio antes de los capitols, no mencionó las abreviaturas, tenemos una solución para eso en el bacalao de producción
johnny 5
¿Puedes mostrar la solución? Tengo cadenas como esta en mis datos y me está dando un resultado incorrecto. Gracias.
Patee Gutee
@PateeGutee Lo siento, leí mal lo que querías. La pluralización es una cuestión diferente, 'RegularOTs', ¿qué esperas que suceda? "OTs regulares" u "OTs regulares"
johnny 5
1
@PateeGutee He actualizado mi respuesta para usted, creo que debería funcionar
Johnny 5
1
replaceAll("(?<=[^^\\p{Uppercase}])(?=[\\p{Uppercase}])"," ");
Randyaa
fuente
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
fuente
1

En Ruby, a través de Regexp:

"FooBarBaz".gsub(/(?!^)(?=[A-Z])/, ' ') # => "Foo Bar Baz"
Artem
fuente
1
Ups, lo siento. Me perdí que es una pregunta específica de C # y publiqué aquí la respuesta de Ruby :(
Artem
1

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

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

La pregunta es un poco antigua, pero hoy en día hay una buena biblioteca en Nuget que hace exactamente esto, así como muchas otras conversiones a 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"
Jonas Pegerfalk
fuente
Solo intenté eso y el primer enlace ahora está roto. NuGet funciona, pero el paquete no se compila en mi solución. Una buena idea, si funcionó.
philw
1

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();
Dave Cousineau
fuente
0

Además de la respuesta de Martin Brown, también tuve un problema con los números. Por ejemplo: "Location2" o "Jan22" deben 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 sitios geniales para descubrir qué significa cada parte también:

Analizador de expresiones regulares basado en Java (pero funciona para la mayoría de expresiones regulares .net)

Analizador basado en script de acción

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

Daryl
fuente
0

Aquí está mi solución, basada en la sugerencia y construcción de Binary Worriers en los comentarios de Richard Priddys, pero también teniendo en cuenta que puede existir espacio en blanco en la cadena provista, por lo que no agregará espacio en blanco junto al 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();
    }
Yetiish
fuente
0

Para cualquiera que esté buscando una función C ++ que responda a esta misma pregunta, puede usar lo siguiente. Esto se basa en la respuesta dada por @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" -> "hello World"
  • "HelloWorld" -> "Hello World"
  • "HelloABCWorld" -> "Hello ABC World"
  • "HelloWorldABC" -> "Hello World ABC"
  • "ABCHelloWorld" -> "ABC Hello World"
  • "ABC HOLA MUNDO" -> "ABC HOLA MUNDO"
  • "ABCHELLOWORLD" -> "ABCHELLOWORLD"
  • "A" -> "A"
lbrendanl
fuente
0

Una solución C # para una cadena de entrada que consta solo de caracteres ASCII. La expresión regular incorpora una mirada hacia atrás negativa para ignorar una letra mayúscula (mayúscula) que aparece al comienzo de la cadena. Utiliza Regex.Replace () para devolver la cadena deseada.

También vea la demostración 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);
    }
}

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

Consulte también la demostración de regex101.com y la demostración de 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);
    }
}

Rendimiento esperado:

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

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

Nota: He usado múltiples expresiones regulares (no conciso, pero también manejará siglas y palabras de una sola 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

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.)
CrazyTim
fuente
Esto genera "Esta cadena no tiene espacios pero tiene mayúsculas"
Andy Robinson
Hola @AndyRobinson, gracias. Cambié para usar múltiples reemplazos Regex. No estoy seguro si hay una manera más concisa, pero funciona ahora.
CrazyTim
0

Todas las respuestas anteriores parecían demasiado complicadas.

Tenía una cadena que tenía una combinación de mayúsculas y _ así que, string.Replace () para hacer _, "" y usé lo siguiente para agregar un espacio en 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.
        }
    }
}
st3_121
fuente
0

Inspirado por la respuesta de Binary Worrier, di un giro ante esto.

Aquí está el 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();
}

Hice la prueba con 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 de Binary Worrier.

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

Este incluye acrónimos y acrónimos en plural 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";
Serj Sagan
fuente
la respuesta aceptada se ocupa del caso donde el valor es nulo
Chris F Carroll
Esto agrega un espacio adicional frente a la salida, es decir, HireDate => "Hire Date". Necesita un final.TrimStart o algo así. Creo que eso es lo que una de las otras respuestas señala a continuación, pero debido al reordenamiento, no estoy seguro de si estaba hablando con usted, ya que su respuesta se basa en RegEx.
b_levitt
Buena captura ... debería haber agregado un marcador de inicio y fin a mis pruebas ... arreglado ahora.
Serj Sagan
Similar a otra solución publicada aquí, falla con la cadena "RegularOTs". Devuelve "Regular O Ts"
Patee Gutee
Gracias por mencionar los abreviaturas en plural, también he actualizado para trabajar para esto.
Serj Sagan el
0

Una implementación con fold, también conocida 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());

Además de la solicitud, esta implementación guarda correctamente espacios y acrónimos iniciales, internos y finales, por ejemplo,

" SpacedWord " => " Spaced Word ",  

"Inner Space" => "Inner Space",  

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

Una manera simple de agregar espacios después de letras minúsculas, letras mayúsculas o 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;
    }
Príncipe Owusu
fuente
1
Se desaconsejan las respuestas de solo código. Haga clic en editar y agregue algunas palabras que resuman cómo su código aborda la pregunta, o tal vez explique cómo su respuesta difiere de la respuesta / respuestas anteriores. De la opinión
Nick