Fügen Sie Leerzeichen vor Großbuchstaben ein


193

Was ist angesichts der Zeichenfolge "ThisStringHasNoSpacesButItDoesHaveCapitals" der beste Weg, Leerzeichen vor den Großbuchstaben hinzuzufügen. Die Endzeichenfolge wäre also "Diese Zeichenfolge hat keine Leerzeichen, aber Großbuchstaben".

Hier ist mein Versuch mit einem RegEx

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

2
Haben Sie eine besondere Beschwerde über Ihren Ansatz? Das könnte uns helfen, Ihre Methode zu verbessern.
Blair Conrad

Wenn die Regex funktioniert, würde ich dabei bleiben. Regex ist für die Manipulation von Zeichenfolgen optimiert.
Michael Meadows

Ich bin nur neugierig, ob es einen besseren oder vielleicht sogar einen eingebauten Ansatz gibt. Ich wäre sogar neugierig, andere Ansätze mit anderen Sprachen zu sehen.
Bob

2
Ihr Code hat einfach nicht funktioniert, da die geänderte Zeichenfolge der Rückgabewert der Funktion 'Ersetzen' ist. Mit dieser Codezeile: 'System.Text.RegularExpressions.Regex.Replace (Wert "[AZ]", "$ 0"). Trim ();' es würde perfekt funktionieren. (Nur kommentieren, weil ich über diesen Beitrag gestolpert bin und niemand wirklich gesehen hat, was mit Ihrem Code nicht
stimmte

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

Antworten:


203

Die regulären Ausdrücke werden gut funktionieren (ich habe sogar die Antwort von Martin Browns gewählt), aber sie sind teuer (und ich persönlich finde jedes Muster länger als ein paar Zeichen, die unerschwinglich stumpf sind).

Diese Funktion

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

Wird es 100.000 Mal in 2.968.750 Ticks tun, wird der Regex 25.000.000 Ticks nehmen (und das ist mit dem kompilierten Regex).

Es ist besser, für einen bestimmten Wert von besser (dh schneller), aber es ist mehr Code zu pflegen. "Besser" ist oft ein Kompromiss zwischen konkurrierenden Anforderungen.

Hoffe das hilft :)

Aktualisieren
Es ist lange her, dass ich mir das angesehen habe, und ich habe gerade festgestellt, dass die Timings nicht aktualisiert wurden, seit sich der Code geändert hat (es hat sich nur wenig geändert).

Bei einer Zeichenfolge, bei der 'Abbbbbbbbb' 100 Mal wiederholt wird (dh 1.000 Byte), werden bei einer Konvertierung von 100.000 Konvertierungen die handcodierte Funktion 4.517.177 Ticks ausgeführt, und bei der folgenden Regex werden 59.435.719 Ticks ausgeführt, sodass die handcodierte Funktion in 7,6% der Zeit ausgeführt wird Regex.

Update 2 Werden Akronyme berücksichtigt? Es wird jetzt! Die Logik der if-Anweisung ist ziemlich dunkel, wie Sie sehen können, wenn Sie sie auf diese ...

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

... hilft überhaupt nicht!

Hier ist die ursprüngliche einfache Methode, die sich nicht um Akronyme kümmert

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]! = '') Wenn Sie den obigen Code erneut ausführen, werden weiterhin Leerzeichen hinzugefügt, wodurch das Hinzufügen von Leerzeichen verhindert wird, wenn vor dem Großbuchstaben ein Leerzeichen steht Brief.
Paul Talbot

Ich bin mir nicht sicher, also dachte ich, ich würde fragen, ob diese Methode mit Akronymen umgeht, wie in Martin Browns Antwort beschrieben. "DriveIsSCSICompatible" würde idealerweise zu "Drive Is SCSI Compatible" werden
Coops

Das hat es zu einem Zeichen gemacht, indem ich den Inhalt Ihrer for-Anweisung durch die neu aktualisierten if-Anweisungen ersetzt habe. Kann ich etwas falsch machen?
Coops

1
Das Hinzufügen eines Häkchens für char.IsLetter (Text [i + 1]) hilft bei Akronymen mit Sonderzeichen und Ziffern (dh ABC_DEF wird nicht als AB C_DEF aufgeteilt).
HeXanon

1
Ich bin nicht sicher, ob der Akronymteil korrekt ist, wenn er ausgeschaltet ist. Ich habe gerade einen Test durchgeführt. "ASentenceABC" wird zu "ASentence AB C" erweitert. Sollte "Ein Satz AB C" sein
Tim Rutter

149

Ihre Lösung hat das Problem, dass vor dem ersten Buchstaben T ein Leerzeichen steht, damit Sie erhalten

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

Um dies zu umgehen, suchen Sie auch nach dem vorangestellten Kleinbuchstaben und fügen Sie dann das Leerzeichen in der Mitte ein:

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

Bearbeiten 1:

Wenn Sie es verwenden @"(\p{Ll})(\p{Lu})", werden auch Zeichen mit Akzent aufgenommen.

Bearbeiten 2:

Wenn Ihre Zeichenfolgen Akronyme enthalten können, können Sie Folgendes verwenden:

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

"DriveIsSCSICompatible" wird also zu "Drive Is SCSI Compatible".


3
Könnten Sie nicht auch einfach das Original RegEx behalten und das Ergebnis trimmen ()?
PandaWood

3
@PandaWood könnten Sie, aber es würde eine andere Speicherzuordnung und String-Kopie erfordern. Das heißt, wenn Leistung ein Problem ist, ist ein Regex wahrscheinlich sowieso nicht der beste Weg.
Martin Brown

Könnten Sie auch "([^A-Z\\s])([A-Z])"mit Akronymen verwenden?
Ruben9922

82

Leistung nicht getestet, aber hier in einer Zeile mit linq:

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

18

Ich weiß, dass dies eine alte ist, aber dies ist eine Erweiterung, die ich verwende, wenn ich dies tun muss:

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

Dies ermöglicht Ihnen die Verwendung MyCasedString.ToSentence()


Ich mag die Idee, dass dies eine Erweiterungsmethode ist. Wenn Sie sie hinzufügen TrimStart(' '), wird der führende Leerzeichen entfernt.
user1069816

1
Danke @ user1069816. Ich habe die Erweiterung geändert, um deren Überlastung zu verwenden, SelectManydie einen Index enthält. Auf diese Weise werden der erste Buchstabe und der unnötige potenzielle Aufwand für einen zusätzlichen Aufruf von vermieden TrimStart(' '). Rauben.
Rob Hardy

8

Willkommen bei Unicode

Alle diese Lösungen sind für modernen Text im Wesentlichen falsch. Sie müssen etwas verwenden, das den Fall versteht. Da Bob nach anderen Sprachen gefragt hat, gebe ich ein paar für Perl.

Ich biete vier Lösungen an, die vom schlechtesten bis zum besten reichen. Nur der Beste hat immer Recht. Die anderen haben Probleme. Hier ist ein Testlauf, der Ihnen zeigt, was funktioniert und was nicht und wo. Ich habe Unterstriche verwendet, damit Sie sehen können, wo die Leerzeichen platziert wurden, und ich habe alles, was falsch ist, als falsch markiert.

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

Übrigens hat fast jeder hier den ersten Weg gewählt, den mit "Schlimmsten" gekennzeichneten. Einige haben den zweiten Weg mit "OK" gewählt. Aber niemand vor mir hat Ihnen gezeigt, wie man entweder den "besseren" oder den "besten" Ansatz macht.

Hier ist das Testprogramm mit seinen vier Methoden:

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

Wenn Sie in diesem Datensatz das gleiche Ergebnis wie "Best" erzielen können, wissen Sie, dass Sie es richtig gemacht haben. Bis dahin hast du nicht. Niemand hier hat es besser gemacht als "Ok", und die meisten haben es "am schlechtesten" gemacht. Ich freue mich darauf, jemanden zu sehen, der den richtigen post-Code veröffentlicht.

Ich stelle fest, dass der Hervorhebungscode von StackOverflow wieder miserabel dumm ist. Sie machen alle die gleichen alten Lahmheiten wie (die meisten, aber nicht alle) der anderen armen Ansätze, die hier erwähnt wurden. Ist es nicht lange her, ASCII zur Ruhe zu bringen? Es macht keinen Sinn mehr und so zu tun, als wäre es alles, was Sie haben, ist einfach falsch. Es macht für schlechten Code.


Ihre "beste" Antwort scheint die bisher am nächsten liegende zu sein, aber es scheint nicht, dass sie für führende Interpunktion oder andere führende Nicht-Kleinbuchstaben verantwortlich ist. Dies scheint für mich am besten zu funktionieren (in Java): replaceAll ("(? <= [^^ \\ p {javaUpperCase}]) (? = [\\ p {javaUpperCase}])", "");
Randyaa

Hmm. Ich bin mir nicht sicher, ob römische Ziffern in diesem Beispiel wirklich als Großbuchstaben gelten sollten. Das Beispiel für den Buchstabenmodifikator sollte definitiv nicht gezählt werden. Wenn Sie zu McDonalds.com gehen, werden Sie sehen, dass es ohne Leerzeichen geschrieben ist.
Martin Brown

Es sollte auch beachtet werden, dass Sie dies niemals perfekt machen werden. Zum Beispiel würde ich gerne ein Beispiel sehen, das "AlexandervonHumboldt" aussortiert, das als "Alexander von Humboldt" enden sollte. Dann gibt es natürlich Sprachen, die nicht die Unterscheidung zwischen Groß- und Kleinschreibung haben.
Martin Brown

8

Ich habe mich vorgenommen, eine einfache Erweiterungsmethode zu erstellen, die auf dem Code von Binary Worrier basiert und Akronyme richtig verarbeitet und wiederholbar ist (bereits verteilte Wörter werden nicht entstellt). Hier ist mein Ergebnis.

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

Hier sind die Unit-Testfälle, die diese Funktion besteht. Ich habe die meisten von tchrist vorgeschlagenen Fälle zu dieser Liste hinzugefügt. Die drei von denen, die es nicht besteht (zwei sind nur römische Ziffern), sind auskommentiert:

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

Ähnlich wie bei anderen hier veröffentlichten Lösungen schlägt dies mit der Zeichenfolge "RegularOTs" fehl. Es gibt "Regular O Ts"
Patee Gutee

4

Binary Worrier, ich habe Ihren vorgeschlagenen Code verwendet, und es ist ziemlich gut, ich habe nur eine kleine Ergänzung dazu:

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

Ich habe eine Bedingung hinzugefügt !char.IsUpper(text[i - 1]). Dies behebt einen Fehler, der dazu führen würde, dass etwas wie "AverageNOX" in "Average NO X" umgewandelt wird, was offensichtlich falsch ist, da es "Average NOX" lauten sollte.

Leider hat dies immer noch den Fehler, dass Sie, wenn Sie den Text 'FromAStart' haben, 'From AStart' herausbekommen würden.

Irgendwelche Gedanken zur Behebung dieses Problems?


Vielleicht würde so etwas funktionieren: char.IsUpper (Text [i]) && (char.IsLower (Text [i - 1]) || (char.IsLower (Text [i + 1]))
Martin Brown

1
Dies ist das richtige: if (char.IsUpper(text[i]) && !(char.IsUpper(text[i - 1]) && char.IsUpper(text[i + 1])))Testergebnis: "Von Anfang an", "Von Anfang an", "Von Anfang an", aber Sie müssen i < text.Length - 1in der for-Schleifenbedingung das letzte Zeichen ignorieren und eine Ausnahme außerhalb des Bereichs verhindern.
CallMeLaNN

Oh, es ist trotzdem so. ! (a && b) und (! a ||! b) weil unten =! oben.
CallMeLaNN

3

Hier ist meins:

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

Soll das C # sein? Wenn ja, in welchem ​​Namespace befindet sich List? Meinen Sie ArrayList oder List <string>?
Martin Brown

Liste <string> wäre in Ordnung. Das tut mir leid.
Cory Foy

@Martin Er hatte immer die richtige Syntax, sie wurde nur in einem <pre><code>code</code></pre>Block anstelle der Markdown-Syntax versteckt . Keine Notwendigkeit, ihn abzustimmen (wenn das du warst).
George Stocker

3

Stellen Sie sicher , Sie sind nicht setzen Leerzeichen am Anfang des Strings, aber Sie werden sie zwischen aufeinanderfolgenden Großbuchstaben setzen. Einige der Antworten hier sprechen nicht einen oder beide dieser Punkte an. Es gibt andere Möglichkeiten als Regex, aber wenn Sie dies bevorzugen, versuchen Sie Folgendes:

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

Das \Bist ein negiertes \b, also stellt es eine Nicht-Wort-Grenze dar. Dies bedeutet, dass das Muster mit "Y" übereinstimmt XYzabc, jedoch nicht mit Yzabcoder X Yzabc. Als kleinen Bonus können Sie dies für eine Zeichenfolge mit Leerzeichen verwenden, ohne diese zu verdoppeln.


3

Dieser Regex setzt ein Leerzeichen vor jeden Großbuchstaben:

using System.Text.RegularExpressions;

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

Achten Sie auf den Platz vor "$ 1 $ 2", dies wird es tun.

Dies ist das Ergebnis:

"This Is A String Without Spaces"

1
Wenn Sie möchten, dass Zahlen auch getrennt werden, verwenden Sie stattdessen dieses Regex-Muster:"([A-Z0-9])([a-z]*)"
Matthias Thomann

2

Was Sie haben, funktioniert perfekt. Denken Sie daran, valueden Rückgabewert dieser Funktion neu zuzuweisen .

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

2

Hier erfahren Sie, wie Sie dies in SQL tun können

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

Inspiriert von @MartinBrown, Two Lines of Simple Regex, das Ihren Namen auflöst, einschließlich Acyronyms an einer beliebigen Stelle in der Zeichenfolge.

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

Ich mag diese Lösung. Es ist kurz und schnell. Ähnlich wie bei anderen Lösungen schlägt es jedoch mit der Zeichenfolge "RegularOTs" fehl. Jede Lösung, die ich hier versucht habe, gibt "Regular O Ts"
Patee Gutee

@PateeGutee das OP wollte Platz vor Kapitolen, er erwähnte keine Abkürzungen, wir haben eine Lösung dafür in der Produktion cod
Johnny 5

Können Sie das Update zeigen? Ich habe solche Zeichenfolgen in meinen Daten und es gibt mir ein falsches Ergebnis. Vielen Dank.
Patee Gutee

@PateeGutee Entschuldigung, ich habe falsch verstanden, was Sie wollten. Pluralisierung ist ein anderes Thema, "RegularOTs", was erwarten Sie "Regular OTs" oder "Regular OT s"
Johnny 5

1
@PateeGutee Ich habe meine Antwort für Sie aktualisiert, ich glaube, das sollte funktionieren
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 über Regexp:

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

1
Ups, tut mir Leid. Ich habe vermisst, dass es C # -spezifische Frage ist und hier gepostet Ruby Antwort :(
Artem

1

Ich nahm Kevin Strikers ausgezeichnete Lösung und konvertierte zu VB. Da ich an .NET 3.5 gebunden bin, musste ich auch IsNullOrWhiteSpace schreiben. Dies besteht alle seine Tests.

<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

Die Frage ist etwas alt, aber heutzutage gibt es auf Nuget eine nette Bibliothek, die genau dies tut, sowie viele andere Konvertierungen in von Menschen lesbaren Text.

Schauen Sie sich Humanizer auf GitHub oder Nuget an.

Beispiel

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

Habe das gerade versucht und der erste Link ist jetzt kaputt. NuGet funktioniert, aber das Paket wird in meiner Lösung nicht kompiliert. Eine schöne Idee, wenn es funktioniert hat.
Philw

1

Scheint eine gute Gelegenheit für Aggregate. Dies soll lesbar sein, nicht unbedingt besonders schnell.

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

0

Zusätzlich zu Martin Browns Antwort hatte ich auch ein Problem mit Zahlen. Zum Beispiel: "Location2" oder "Jan22" sollte "Location 2" bzw. "Jan 22" sein.

Hier ist mein regulärer Ausdruck dafür, der Martin Browns Antwort verwendet:

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

Hier sind ein paar großartige Seiten, um herauszufinden, was jeder Teil auch bedeutet:

Java Based Regular Expression Analyzer (funktioniert aber für die meisten .net-Regex)

Action Script Based Analyzer

Der obige reguläre Ausdruck funktioniert auf der Aktionsskript-Site nur, wenn Sie alle \p{Ll}mit [a-z], die \p{Lu}mit [A-Z]und \p{Nd}mit ersetzen [0-9].


0

Hier ist meine Lösung, basierend auf dem Vorschlag von Binary Worriers und dem Einbau von Richard Priddys 'Kommentaren, aber auch unter Berücksichtigung, dass in der bereitgestellten Zeichenfolge möglicherweise Leerzeichen vorhanden sind, sodass kein Leerzeichen neben dem vorhandenen Leerzeichen hinzugefügt wird.

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

Für alle, die nach einer C ++ - Funktion suchen, die dieselbe Frage beantwortet, können Sie Folgendes verwenden. Dies ist der Antwort von @Binary Worrier nachempfunden. Diese Methode behält Akronyme nur automatisch bei.

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;

Die Testzeichenfolgen, die ich für diese Funktion verwendet habe, und die Ergebnisse sind:

  • "helloWorld" -> "hallo Welt"
  • "HelloWorld" -> "Hello World"
  • "HelloABCWorld" -> "Hallo ABC World"
  • "HelloWorldABC" -> "Hello World ABC"
  • "ABCHelloWorld" -> "ABC Hello World"
  • "ABC HELLO WORLD" -> "ABC HELLO WORLD"
  • "ABCHELLOWORLD" -> "ABCHELLOWORLD"
  • "A" -> "A"

0

Eine C # -Lösung für eine Eingabezeichenfolge, die nur aus ASCII-Zeichen besteht. Die Regex enthält ein negatives Lookbehind , um einen Großbuchstaben (Großbuchstaben) zu ignorieren, der am Anfang der Zeichenfolge angezeigt wird. Verwendet Regex.Replace () , um die gewünschte Zeichenfolge zurückzugeben.

Siehe auch die Demo zu 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);
    }
}

Erwartete Ausgabe:

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

Update: Hier ist eine Variante, die auch Akronyme (Sequenzen von Großbuchstaben) behandelt.

Siehe auch die Demo von regex101.com und die Demo von 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);
    }
}

Erwartete Ausgabe:

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

0

Hier ist eine gründlichere Lösung, bei der keine Leerzeichen vor den Wörtern stehen:

Hinweis: Ich habe mehrere Regexs verwendet (nicht präzise, ​​aber es werden auch Akronyme und Wörter mit einem Buchstaben verarbeitet).

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"

Out :

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

Dies gibt "Diese Zeichenfolge hat keine Leerzeichen, aber sie hat Großbuchstaben"
Andy Robinson

Hallo @AndyRobinson, danke. Ich habe mehrere Regex-Ersetzungen verwendet. Ich bin mir nicht sicher, ob es einen prägnanteren Weg gibt, aber er funktioniert jetzt.
CrazyTim

0

Alle vorherigen Antworten sahen zu kompliziert aus.

Ich hatte eine Zeichenfolge, die eine Mischung aus Großbuchstaben und _ so verwendet, string.Replace (), um das _, "" zu erstellen, und verwendete das Folgende, um ein Leerzeichen an den Großbuchstaben hinzuzufügen.

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

Inspiriert von der Antwort von Binary Worrier nahm ich einen Schwung.

Hier ist das Ergebnis:

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

Test mit Stoppuhr mit 10000000 Iterationen und verschiedenen Stringlängen und -kombinationen.

Im Durchschnitt 50% (vielleicht etwas mehr) schneller als die Antwort von 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

Dieser enthält Akronyme und Akronympluralformen und ist etwas schneller als die akzeptierte Antwort:

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

Besteht diese Tests:

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

Die akzeptierte Antwort befasst sich mit dem Fall, in dem der Wert null ist
Chris F Carroll

Dies fügt einen zusätzlichen Platz vor der Ausgabe hinzu, dh HireDate => "Hire Date". Benötigt einen final.TrimStart oder so. Ich denke, das ist es, worauf eine der anderen Antworten unten hinweist, aber wegen der Neuordnung bin ich mir nicht sicher, ob er mit Ihnen gesprochen hat, da seine Antwort auf RegEx basiert.
b_levitt

Guter Fang ... hätte meinen Tests einen Start- und Endmarker hinzufügen sollen ... jetzt behoben.
Serj Sagan

Ähnlich wie bei anderen hier veröffentlichten Lösungen schlägt dies mit der Zeichenfolge "RegularOTs" fehl. Es gibt "Regular O Ts"
Patee Gutee

Vielen Dank, dass Sie die Abkürzungspluralform angesprochen haben. Ich habe aktualisiert, um auch dafür zu arbeiten.
Serj Sagan

0

Eine Implementierung mit fold, auch bekannt als 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());

Zusätzlich zur Anforderung speichert diese Implementierung korrekt führende, innere, nachfolgende Leerzeichen und Akronyme, z.

" SpacedWord " => " Spaced Word ",  

"Inner Space" => "Inner Space",  

"SomeACRONYM" => "Some ACRONYM".

0

Eine einfache Möglichkeit, Leerzeichen nach Kleinbuchstaben, Großbuchstaben oder Ziffern einzufügen.

    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
Von Nur-Code-Antworten wird abgeraten. Klicken Sie auf Bearbeiten und fügen Sie einige Wörter hinzu, die zusammenfassen, wie Ihr Code die Frage beantwortet, oder erklären Sie möglicherweise, wie sich Ihre Antwort von der vorherigen Antwort / den vorherigen Antworten unterscheidet. Aus dem Rückblick
Nick
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.