Aggiungi spazi prima delle lettere maiuscole


194

Data la stringa "ThisStringHasNoSpacesButItDoesHaveCapitals" qual è il modo migliore per aggiungere spazi prima delle lettere maiuscole. Quindi la stringa finale sarebbe "Questa stringa non ha spazi ma ha maiuscole"

Ecco il mio tentativo con un RegEx

System.Text.RegularExpressions.Regex.Replace(value, "[A-Z]", " $0")

2
Hai un reclamo particolare riguardo all'approccio che hai adottato? Questo potrebbe aiutarci a migliorare il tuo metodo.
Blair Conrad

Se il regex funziona, allora continuerei con quello. Regex è optamizzato per la manipolazione di stringhe.
Michael Meadows

Sono solo curioso se esiste un approccio migliore o forse addirittura integrato. Sarei anche curioso di vedere altri approcci con altre lingue.
Bob,

2
Il tuo codice semplicemente non ha funzionato perché la stringa modificata è il valore di ritorno della funzione 'Sostituisci'. Con questa riga di codice: 'System.Text.RegularExpressions.Regex.Replace (value, "[AZ]", "$ 0"). Trim ();' funzionerebbe perfettamente. (Sto solo commentando perché mi sono imbattuto in questo post e nessuno ha visto davvero, cosa c'era di sbagliato nel tuo codice.)
Mattu475

Regex.Replace ("ThisStringHasNoSpacesButItDoesHaveCapitals", @ "\ B [AZ]", m => "" + m);
saquib adil,

Risposte:


203

Le regex funzioneranno benissimo (ho persino votato la risposta di Martin Browns), ma sono costose (e personalmente trovo qualsiasi schema più lungo di un paio di personaggi proibitivamente ottusi)

Questa funzione

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 farà 100.000 volte in 2.968.750 tick, il regex prenderà 25.000.000 di tick (e questo è con il regex compilato).

È meglio, per un dato valore di meglio (cioè più veloce) tuttavia è più codice da mantenere. "Migliore" è spesso un compromesso tra requisiti concorrenti.

Spero che questo ti aiuti :)

Aggiornamento
È da un po 'di tempo che non ci guardo e ho appena capito che i tempi non sono stati aggiornati da quando il codice è cambiato (è cambiato solo un po').

Su una stringa con 'Abbbbbbbbb' ripetuta 100 volte (ovvero 1.000 byte), una corsa di 100.000 conversioni prende la funzione codificata a mano 4.517.177 tick e il Regex sottostante impiega 59.435.719 facendo funzionare la funzione codificata a mano nel 7,6% delle volte regex.

Aggiornamento 2 Prenderà in considerazione gli acronimi? Lo farà ora! La logica dell'istruzione if è abbastanza oscura, come puoi vedere espandendola a questo ...

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

... non aiuta affatto!

Ecco il metodo semplice originale che non si preoccupa degli acronimi

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

8
if (char.IsUpper (text [i]) && text [i - 1]! = '') Se riesegui il codice sopra continua ad aggiungere spazi, questo impedirà l'aggiunta di spazi se c'è uno spazio prima della maiuscola lettera.
Paul Talbot,

Non sono sicuro, quindi ho pensato di chiedere, questo metodo gestisce gli acronimi come descritto nella risposta di Martin Brown "DriveIsSCSICompatible" sarebbe idealmente diventato "Drive Is SCSI Compatible"
Coops

Ciò ha reso 1 carattere sostituendo il contenuto della tua dichiarazione for con le dichiarazioni if ​​appena aggiornate, potrei fare qualcosa di sbagliato?
Coop il

1
L'aggiunta di un controllo per char.IsLetter (testo [i + 1]) aiuta con gli acronimi con caratteri e cifre speciali (cioè ABC_DEF non verrà diviso come AB C_DEF).
HeXanon,

1
Non sono sicuro che la parte degli acronimi sia corretta quando è disattivata. Ho appena eseguito un test "ASentenceABC" si espande in "ASentence AB C". Dovrebbe essere "A Frase AB C"
Tim Rutter,

150

La tua soluzione ha un problema in quanto mette uno spazio prima della prima lettera T in modo da ottenere

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

Per aggirare questo aspetto, cerca anche la lettera minuscola che la precede, quindi inserisci lo spazio nel mezzo:

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

Modifica 1:

Se usi @"(\p{Ll})(\p{Lu})" raccoglierà anche personaggi accentati.

Modifica 2:

Se le tue stringhe possono contenere acronimi, ti consigliamo di utilizzare questo:

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

Quindi "DriveIsSCSICompatible" diventa "Drive Is SCSI Compatible"


3
Non potresti anche mantenere il RegEx originale e Trim () il risultato?
PandaWood,

3
@PandaWood potresti ma richiederebbe un'altra allocazione di memoria e copia di stringa. Detto questo, se le prestazioni sono una preoccupazione, probabilmente un Regex non è il modo migliore di procedere.
Martin Brown,

Potresti anche usare "([^A-Z\\s])([A-Z])", anche con gli acronimi?
Ruben9922,

82

Non ho testato le prestazioni, ma qui in una riga con linq:

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

18

So che questo è vecchio, ma questa è un'estensione che uso quando devo farlo:

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

Questo ti permetterà di usare MyCasedString.ToSentence()


Mi piace l'idea di questo come metodo di estensione, se lo aggiungi TrimStart(' ')rimuoverà lo spazio iniziale.
user1069816

1
Grazie @ user1069816. Ho modificato l'estensione per utilizzare il sovraccarico di SelectManycui include un indice, in questo modo evita la prima lettera e il sovraccarico potenziale non necessario di una chiamata aggiuntiva a TrimStart(' '). Rapinare.
Rob Hardy,

9

Ho deciso di creare un semplice metodo di estensione basato sul codice di Binary Worrier che gestirà correttamente gli acronimi ed è ripetibile (non manipolerà le parole già spaziate). Ecco il mio risultato

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

Ecco i casi di test unitari passati da questa funzione. Ho aggiunto la maggior parte dei casi suggeriti da tchrist a questo elenco. I tre di quelli che non passa (due sono solo numeri romani) sono commentati:

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

Simile ad altre soluzioni pubblicate qui, non riesce con la stringa "RegularOTs". Restituisce "Regular O Ts"
Patee Gutee il

8

Benvenuto in Unicode

Tutte queste soluzioni sono essenzialmente sbagliate per il testo moderno. Devi usare qualcosa che capisca il caso. Dato che Bob ha chiesto altre lingue, ne darò un paio per il Perl.

Fornisco quattro soluzioni, che vanno dal peggiore al migliore. Solo il migliore ha sempre ragione. Gli altri hanno problemi. Ecco un test per mostrarti cosa funziona, cosa no e dove. Ho usato i trattini bassi in modo da poter vedere dove sono stati posizionati gli spazi e ho contrassegnato come sbagliato tutto ciò che è, beh, sbagliato.

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

A proposito, quasi tutti qui hanno selezionato il primo modo, quello contrassegnato con "Peggiore". Alcuni hanno selezionato il secondo modo, contrassegnato con "OK". Ma nessun altro prima di me ti ha mostrato come fare l'approccio "Migliore" o "Migliore".

Ecco il programma di test con i suoi quattro metodi:

#!/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 puoi ottenere lo stesso punteggio del "Migliore" in questo set di dati, saprai di averlo eseguito correttamente. Fino ad allora, non l'hai fatto. Nessun altro qui ha fatto meglio di "Ok", e molti lo hanno fatto "Peggiore". Non vedo l'ora di vedere qualcuno pubblicare il codice correct corretto.

Noto che il codice di evidenziazione di StackOverflow è miseramente di nuovo stoopido. Stanno facendo lo stesso vecchio zoppo di (la maggior parte ma non tutti) degli altri approcci poveri citati qui. Non è passato molto tempo per mettere a riposo ASCII? Non ha più senso, e fingere che tutto ciò che hai è semplicemente sbagliato. Rende il codice errato.


la tua risposta "migliore" sembra finora la più vicina, ma non sembra che rappresenti la punteggiatura iniziale o altre lettere non minuscole. Questo sembra funzionare meglio per me (in java): replaceAll ("(? <= [^^ \\ p {javaUpperCase}]) (? = [\ \ P {javaUpperCase}])", "");
Randyaa,

Hmm. Non sono sicuro che i numeri romani debbano davvero essere considerati maiuscoli in questo esempio. L'esempio lettera modificatore non dovrebbe assolutamente essere conteggiato. Se vai su McDonalds.com vedrai che è scritto senza spazio.
Martin Brown,

Va anche notato che non riuscirai mai a renderlo perfetto. Ad esempio, vorrei vedere un esempio che ordina "AlexandervonHumboldt", che dovrebbe finire come "Alexander von Humboldt". Poi ci sono ovviamente lingue che non hanno la distinzione di maiuscolo e minuscolo.
Martin Brown,

4

Binary Worrier, ho usato il tuo codice suggerito, ed è piuttosto buono, ho solo un'aggiunta minore ad esso:

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

Ho aggiunto una condizione !char.IsUpper(text[i - 1]) . In questo modo è stato corretto un errore che trasformava qualcosa come "AverageNOX" in "NO NO medio", il che è ovviamente errato, in quanto dovrebbe leggere "NOX medio".

Purtroppo questo ha ancora il bug che se si ha il testo 'FromAStart', si otterrebbe 'From AStart'.

Qualche idea su come risolvere questo?


Forse qualcosa del genere funzionerebbe: char.IsUpper (testo [i]) && (char.IsLower (testo [i - 1]) || (char.IsLower (testo [i + 1]))
Martin Brown

1
Questo è quello corretto: if (char.IsUpper(text[i]) && !(char.IsUpper(text[i - 1]) && char.IsUpper(text[i + 1])))Risultato del test: "Dall'inizio", "Dall'inizio", "Dall'inizio", ma è necessario i < text.Length - 1nella condizione del ciclo for ignorare l'ultimo carattere e prevenire l'eccezione fuori intervallo.
CallMeLaNN,

Oh lo stesso. ! (a && b) e (! a ||! b) perché lower =! upper.
CallMeLaNN,

3

Ecco il mio:

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

Dovrebbe essere C #? In tal caso, in quale spazio dei nomi è elencato? Intendi ArrayList o List <string>?
Martin Brown,

Elenco <stringa> andrebbe bene. Mi dispiace per quello.
Cory Foy,

@Martin Ha sempre avuto la sintassi corretta, era semplicemente nascosta in un <pre><code>code</code></pre>blocco invece della sintassi di Markdown. Non c'è bisogno di sottovalutarlo (se eri tu).
George Stocker,

3

Assicurarsi che non sono mettendo gli spazi all'inizio della stringa, ma si stanno mettendoli tra le capitali consecutivi. Alcune delle risposte qui non riguardano uno o entrambi questi punti. Ci sono altri modi oltre a regex, ma se preferisci usarlo, prova questo:

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

È \Bnegato \b, quindi rappresenta un confine senza parole. Significa che il pattern corrisponde a "Y" in XYzabc, ma non in Yzabco X Yzabc. Come piccolo bonus, puoi usarlo su una stringa con spazi all'interno e non li raddoppierà.


3

Questo Regex posiziona un carattere spazio davanti a ogni lettera maiuscola:

using System.Text.RegularExpressions;

const string myStringWithoutSpaces = "ThisIsAStringWithoutSpaces";
var myStringWithSpaces = Regex.Replace(myStringWithoutSpaces, "([A-Z])([a-z]*)", " $1$2");

Badate allo spazio davanti se "$ 1 $ 2", questo è ciò che lo farà.

Questo è il risultato:

"This Is A String Without Spaces"

1
Se vuoi che anche i numeri siano separati, usa invece questo schema regex:"([A-Z0-9])([a-z]*)"
Matthias Thomann

2

Quello che hai funziona perfettamente. Ricorda solo di riassegnare valueal valore restituito di questa funzione.

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

2

Ecco come è possibile farlo in 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

2

Ispirato da @MartinBrown, Two Lines of Simple Regex, che risolverà il tuo nome, compresi gli acronimi in qualsiasi punto della stringa.

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

Mi piace questa soluzione. È breve e veloce. Tuttavia, simile ad altre soluzioni, fallisce con la stringa "RegularOTs". Ogni soluzione che ho provato qui restituisce "Regular O Ts"
Patee Gutee,

@PateeGutee l'OP voleva spazio prima delle capitali, non ha menzionato le abbreviazioni, abbiamo una soluzione per questo nel merluzzo di produzione
johnny 5

Puoi mostrare la correzione? Ho stringhe come questa nei miei dati e mi sta dando risultati errati. Grazie.
Patee Gutee,

@PateeGutee Siamo spiacenti, ho letto male quello che volevi. La pluralizzazione è una questione diversa, "RegularOTs" che cosa ti aspetti che accada "OT regolari" o "OT normali"
johnny 5

1
@PateeGutee Ho aggiornato la mia risposta per te, credo che dovrebbe funzionare
johnny 5

1
replaceAll("(?<=[^^\\p{Uppercase}])(?=[\\p{Uppercase}])"," ");

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

1

In Ruby, tramite Regexp:

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

1
Oops scusa. Ho perso la domanda specifica per C # e ho pubblicato qui la risposta di Ruby :(
Artem,

1

Ho preso la soluzione eccellente di Kevin Strikers e convertito in VB. Da quando sono bloccato in .NET 3.5, ho anche dovuto scrivere IsNullOrWhiteSpace. Questo supera tutti i suoi test.

<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

1

La domanda è un po 'vecchia ma al giorno d'oggi c'è una bella libreria su Nuget che fa esattamente questo così come molte altre conversioni in testo leggibile dall'uomo.

Dai un'occhiata a Humanizer su GitHub o Nuget.

Esempio

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

L'ho appena provato e il primo collegamento è ora interrotto. NuGet funziona, ma il pacchetto non viene compilato nella mia soluzione. Una bella idea, se funzionasse.
philw,

1

Sembra una buona opportunità per Aggregate. Questo è progettato per essere leggibile, non necessariamente particolarmente veloce.

someString
.Aggregate(
   new StringBuilder(),
   (str, ch) => {
      if (char.IsUpper(ch) && str.Length > 0)
         str.Append(" ");
      str.Append(ch);
      return str;
   }
).ToString();

0

Oltre alla risposta di Martin Brown, ho avuto anche un problema con i numeri. Ad esempio: "Posizione2" o "Jan22" devono essere rispettivamente "Posizione 2" e "22 gennaio".

Ecco la mia espressione regolare per farlo, usando la risposta di Martin Brown:

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

Ecco un paio di siti fantastici per capire cosa significhi anche ogni parte:

Analizzatore di espressioni regolari basato su Java (ma funziona per la maggior parte dei regex .net)

Analizzatore basato su script di azione

Quanto sopra regex non funziona sul sito script di azione a meno che non si sostituisce tutto il \p{Ll}con [a-z]il \p{Lu}con [A-Z], e \p{Nd}con [0-9].


0

Ecco la mia soluzione, basata sul suggerimento di Binary Worrier e sulla base dei commenti di Richard Priddys, ma tenendo anche conto che potrebbe esistere spazio bianco nella stringa fornita, in modo che non aggiunga spazio bianco accanto allo spazio bianco esistente.

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

0

Per chiunque sia alla ricerca di una funzione C ++ che risponda a questa stessa domanda, è possibile utilizzare quanto segue. Questo è modellato sulla risposta data da @Binary Worrier. Questo metodo preserva automaticamente gli acronimi.

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;

Le stringhe di test che ho usato per questa funzione e i risultati sono:

  • "ciao mondo" -> "ciao mondo"
  • "HelloWorld" -> "Hello World"
  • "HelloABCWorld" -> "Hello ABC World"
  • "HelloWorldABC" -> "Hello World ABC"
  • "ABCHelloWorld" -> "ABC Hello World"
  • "ABC HELLO WORLD" -> "ABC HELLO WORLD"
  • "ABCHELLOWORLD" -> "ABCHELLOWORLD"
  • "A" -> "A"

0

Una soluzione C # per una stringa di input che consiste solo di caratteri ASCII. La regex incorpora lookbehind negativo per ignorare una lettera maiuscola (maiuscola) che appare all'inizio della stringa. Utilizza Regex.Replace () per restituire la stringa desiderata.

Vedi anche la demo di 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);
    }
}

Uscita prevista:

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

Aggiornamento: ecco una variante che gestirà anche gli acronimi (sequenze di lettere maiuscole).

Vedi anche la demo di regex101.com e la demo di 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);
    }
}

Uscita prevista:

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

0

Ecco una soluzione più completa che non mette gli spazi davanti alle parole:

Nota: ho usato più Regex (non concisi ma gestirà anche acronimi e parole a lettera singola)

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

In :

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

Fuori :

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

Questo produce "Questa stringa non ha spazi ma ha maiuscole"
Andy Robinson,

Ciao @AndyRobinson, grazie. Ho cambiato per utilizzare più sostituti Regex. Non sono sicuro che esista un modo più conciso, ma ora funziona.
CrazyTim

0

Tutte le risposte precedenti sembravano troppo complicate.

Avevo una stringa che aveva una combinazione di maiuscole e _ così ho usato, string.Replace () per creare _, "" e ho usato quanto segue per aggiungere uno spazio alle lettere maiuscole.

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

0

Ispirato dalla risposta di Binary Worrier, ho preso una decisione.

Ecco il risultato:

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

Ha eseguito il test utilizzando il cronometro con 10000000 iterazioni e varie lunghezze e combinazioni di stringhe.

In media il 50% (forse un po 'di più) più velocemente della risposta Binary Worrier.


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

0

Questo include acronimi e plurali di acronimo ed è un po 'più veloce della risposta accettata:

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

Supera questi test:

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

la risposta accettata si occupa del caso in cui il valore è nullo
Chris F Carroll,

Ciò aggiunge uno spazio aggiuntivo di fronte all'output, ad esempio HireDate => "Data di assunzione". Ha bisogno di un final.TrimStart o qualcosa del genere. Penso che sia quello che un'altra delle risposte sta sottolineando di seguito, ma a causa del riordino non sono sicuro che stesse parlando con te poiché la sua risposta è basata su RegEx.
b_levitt,

Buona cattura ... avrebbe dovuto aggiungere un marcatore di inizio e fine ai miei test ... risolto ora.
Serj Sagan,

Simile ad altre soluzioni pubblicate qui, non riesce con la stringa "RegularOTs". Restituisce "Regular O Ts"
Patee Gutee il

Grazie per aver menzionato i plurali delle abbreviazioni, ho aggiornato anche per questo.
Serj Sagan,

0

Un'implementazione con fold, noto anche come 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());

Oltre alla richiesta, questa implementazione salva correttamente spazi e acronimi iniziali, interni, finali, ad esempio,

" SpacedWord " => " Spaced Word ",  

"Inner Space" => "Inner Space",  

"SomeACRONYM" => "Some ACRONYM".

0

Un modo semplice per aggiungere spazi dopo lettere minuscole, lettere maiuscole o cifre.

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

1
Le risposte di solo codice sono scoraggiate. Fai clic su Modifica e aggiungi alcune parole che riassumono in che modo il tuo codice risponde alla domanda, o forse spiega in che modo la tua risposta differisce dalla precedente / risposte. Dalla recensione
Nick
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.