Dodaj spacje przed wielkimi literami


193

Biorąc pod uwagę ciąg „ThisStringHasNoSpacesButItDoesHaveCapitals”, jaki jest najlepszy sposób dodawania spacji przed dużymi literami. Zatem końcowy ciąg to „Ten ciąg nie ma spacji, ale ma wielkie litery”

Oto moja próba z RegEx

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

2
Czy masz konkretną skargę dotyczącą przyjętego podejścia? To może nam pomóc ulepszyć twoją metodę.
Blair Conrad,

Jeśli regex działa, to trzymałbym się tego. Regex jest zoptymalizowany do manipulacji ciągami.
Michael Meadows,

Jestem tylko ciekawy, czy istnieje lepsze, a może nawet wbudowane podejście. Byłbym nawet ciekawy innych podejść do innych języków.
Bob

2
Twój kod po prostu nie działał, ponieważ zmodyfikowany ciąg jest wartością zwracaną przez funkcję „Zamień”. W tym wierszu kodu: „System.Text.RegularExpressions.Regex.Replace (wartość,„ [AZ] ”,„ $ 0 ”). Trim ();” działałoby idealnie. (Tylko komentując, bo potknąłem się o ten post i nikt tak naprawdę nie widział, co jest nie tak z twoim kodem.)
Mattu475,

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

Odpowiedzi:


203

Wyrażenia regularne będą działały dobrze (głosowałem nawet za odpowiedzią Martina Browna), ale są one drogie (i osobiście uważam, że każdy wzór jest dłuższy niż kilka znaków, które są zbyt tępe)

Ta funkcja

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

Zrobi to 100 000 razy w 2966750 tyknięć, regex zajmie 25 000 000 tyknięć (i to po skompilowaniu wyrażenia regularnego).

Jest lepiej, dla danej wartości lepszej (tj. Szybszej), ale to więcej kodu do utrzymania. „Lepszy” to często kompromis konkurencyjnych wymagań.

Mam nadzieję że to pomoże :)

Aktualizacja
Minęło sporo czasu, odkąd na to spojrzałem, i właśnie zdałem sobie sprawę, że czasy nie zostały zaktualizowane, ponieważ kod się zmienił (zmienił się tylko trochę).

W ciągu z powtarzającym się 100 razy „Abbbbbbbbb” (tj. 1000 bajtów) 100 000 konwersji pobiera ręcznie kodowaną funkcję 4517177 tyknięć, a poniższy Regeks zajmuje 59 435 719, dzięki czemu funkcja kodowana ręcznie działa w 7,6% czasu Regex.

Aktualizacja 2 Czy weźmie pod uwagę akronimy? Teraz będzie! Logika instrukcji if jest dość niejasna, ponieważ można ją rozszerzyć do tego ...

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

... wcale nie pomaga!

Oto oryginalna prosta metoda, która nie martwi się o akronimy

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]! = '') Jeśli ponownie uruchomisz powyższy kod, dodaje on spacje, spowoduje to przerwanie dodawania spacji, jeśli przed stolicą jest spacja list.
Paul Talbot

Nie jestem pewien, więc pomyślałem, że zapytam, czy ta metoda obsługuje akronimy, jak opisano w odpowiedzi Martina Browna „DriveIsSCSICompatible” idealnie stałby się „Drive Is SCSI Compatible”
Coops

To sprawiło, że stał się 1 znakiem, zastępując treść twojego polecenia for nowymi aktualizacjami instrukcji if, może robię coś złego?
Coops

1
Dodanie czeku dla char.IsLetter (tekst [i + 1]) pomaga w akronimach ze znakami specjalnymi i cyframi (tj. ABC_DEF nie zostanie podzielony jako AB C_DEF).
HeXanon

1
Nie jestem pewien, czy część akronimów jest poprawna, kiedy jest WYŁĄCZONA. Właśnie przeprowadziłem test „ASentenceABC” rozwija się do „ASentence AB C”. Powinno być „Zdanie AB C”
Tim Rutter

149

Twoje rozwiązanie ma problem polegający na tym, że wstawia spację przed pierwszą literą T, aby uzyskać

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

Aby obejść ten problem, poszukaj również małej litery poprzedzającej ją, a następnie wstaw spację na środku:

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

Edycja 1:

Jeśli użyjesz @"(\p{Ll})(\p{Lu})" go , zbierze także znaki akcentowane.

Edycja 2:

Jeśli twoje ciągi mogą zawierać akronimy, możesz użyć tego:

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

Zatem „DriveIsSCSICompatible” staje się „Drive is SCSI Compatible”


3
Czy nie możesz również zachować oryginalnego RegEx i Trim () wyniku?
PandaWood,

3
@PandaWood możesz, ale wymagałoby to kolejnej alokacji pamięci i kopiowania ciągów. To powiedziawszy, jeśli wydajność jest zmartwieniem, Regex prawdopodobnie nie jest najlepszym rozwiązaniem.
Martin Brown,

Czy możesz także użyć "([^A-Z\\s])([A-Z])", nawet z akronimami?
Ruben9922

82

Nie testowałem wydajności, ale tutaj w jednej linii z linq:

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

18

Wiem, że to stary, ale jest to rozszerzenie, którego używam, gdy muszę to zrobić:

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

Umożliwi to użycie MyCasedString.ToSentence()


Podoba mi się pomysł na to jako metodę rozszerzenia, jeśli TrimStart(' ')go dodasz , usuniesz wiodącą przestrzeń.
user1069816

1
Dzięki @ user1069816. Zmieniłem rozszerzenie, aby użyć przeciążenia, SelectManyktóre zawiera indeks, w ten sposób unika pierwszej litery i niepotrzebnego potencjalnego obciążenia dodatkowego połączenia z TrimStart(' '). Obrabować.
Rob Hardy

9

Postanowiłem stworzyć prostą metodę rozszerzenia opartą na kodzie Binary Worriera, który będzie poprawnie obsługiwał akronimy i jest powtarzalny (nie będzie zniekształcał już spacji). Oto mój wynik.

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

Oto przypadki testów jednostkowych, które funkcja ta spełnia. Do tej listy dodałem większość sugerowanych przypadków tchrista. Trzy z tych, których nie przejdzie (dwa są tylko cyframi rzymskimi) są komentowane:

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

Podobnie jak w przypadku innych opublikowanych tutaj rozwiązań, nie udaje mu się napis „RegularOTs”. Zwraca „Zwykłe O Ts”
Patee Gutee

8

Witamy w Unicode

Wszystkie te rozwiązania są zasadniczo niewłaściwe dla nowoczesnego tekstu. Musisz użyć czegoś, co rozumie wielkość liter. Ponieważ Bob poprosił o inne języki, dam Perlowi parę.

Dostarczam cztery rozwiązania, od najgorszego do najlepszego. Tylko najlepszy jest zawsze odpowiedni. Inni mają problemy. Oto test, aby pokazać, co działa, a co nie i gdzie. Użyłem znaków podkreślenia, aby zobaczyć, gdzie zostały wstawione spacje, i oznaczyłem jako niewłaściwe wszystko, co jest, no cóż, złe.

Testing TheLoneRanger
               Worst:    The_Lone_Ranger
               Ok:       The_Lone_Ranger
               Better:   The_Lone_Ranger
               Best:     The_Lone_Ranger
Testing MountMKinleyNationalPark
     [WRONG]   Worst:    Mount_MKinley_National_Park
     [WRONG]   Ok:       Mount_MKinley_National_Park
     [WRONG]   Better:   Mount_MKinley_National_Park
               Best:     Mount_M_Kinley_National_Park
Testing ElÁlamoTejano
     [WRONG]   Worst:    ElÁlamo_Tejano
               Ok:       El_Álamo_Tejano
               Better:   El_Álamo_Tejano
               Best:     El_Álamo_Tejano
Testing TheÆvarArnfjörðBjarmason
     [WRONG]   Worst:    TheÆvar_ArnfjörðBjarmason
               Ok:       The_Ævar_Arnfjörð_Bjarmason
               Better:   The_Ævar_Arnfjörð_Bjarmason
               Best:     The_Ævar_Arnfjörð_Bjarmason
Testing IlCaffèMacchiato
     [WRONG]   Worst:    Il_CaffèMacchiato
               Ok:       Il_Caffè_Macchiato
               Better:   Il_Caffè_Macchiato
               Best:     Il_Caffè_Macchiato
Testing MisterDženanLjubović
     [WRONG]   Worst:    MisterDženanLjubović
     [WRONG]   Ok:       MisterDženanLjubović
               Better:   Mister_Dženan_Ljubović
               Best:     Mister_Dženan_Ljubović
Testing OleKingHenry
     [WRONG]   Worst:    Ole_King_Henry
     [WRONG]   Ok:       Ole_King_Henry
     [WRONG]   Better:   Ole_King_Henry
               Best:     Ole_King_Henry_
Testing CarlosⅤºElEmperador
     [WRONG]   Worst:    CarlosⅤºEl_Emperador
     [WRONG]   Ok:       CarlosⅤº_El_Emperador
     [WRONG]   Better:   CarlosⅤº_El_Emperador
               Best:     Carlos_Ⅴº_El_Emperador

BTW, prawie wszyscy tutaj wybrali pierwszy sposób, ten oznaczony jako „Najgorszy”. Kilku wybrało drugi sposób, oznaczony „OK”. Ale nikt przede mną nie pokazał ci, jak zastosować podejście „lepsze” lub „najlepsze”.

Oto program testowy z czterema metodami:

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

Jeśli uzyskasz wynik taki sam, jak „Najlepszy” w tym zestawie danych, będziesz wiedział, że zrobiłeś to poprawnie. Do tego czasu nie. Nikt tutaj nie zrobił nic lepszego niż „Ok”, a większość zrobiła to „Najgorszy”. Nie mogę się doczekać, aż ktoś opublikuje poprawny kod ℂ♯.

Zauważyłem, że kod wyróżniający StackOverflow jest znowu żałośnie głupi. Robią to samo stare kulawizny, jak (większość, ale nie wszystkie) spośród pozostałych podejść, o których tu mowa. Czy nie minęło już dużo czasu, aby położyć ASCII na odpoczynek? To już nie ma sensu, a udawanie, że wszystko, co masz, jest po prostu złe. To powoduje zły kod.


Twoja „najlepsza” odpowiedź wydaje się jak dotąd najbliższa, ale nie wydaje się, aby uwzględniała wiodącą interpunkcję lub inne wiodące, nie pisane małymi literami. Wydaje się, że działa to najlepiej dla mnie (w java): replaceAll ("(? <= [^^ \\ p {javaUpperCase}]) (? = [\\ p {javaUpperCase}])", "");
Randyaa

Hmm Nie jestem pewien, czy cyfry rzymskie naprawdę powinny się liczyć jako wielkie litery w tym przykładzie. Przykładowy modyfikator liter zdecydowanie nie powinien być liczony. Jeśli wejdziesz na McDonalds.com, zobaczysz, że jest napisane bez spacji.
Martin Brown,

Należy również zauważyć, że nigdy nie dostaniesz tego jako doskonałego. Na przykład chciałbym zobaczyć przykład, który wyjaśnia „AlexandervonHumboldt”, który powinien zakończyć się jako „Alexander von Humboldt”. Są też oczywiście języki, które nie mają przeznaczenia wielkich i małych liter.
Martin Brown

4

Binarny Worrier, użyłem twojego sugerowanego kodu i jest raczej dobry, mam tylko jeden drobny dodatek:

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

Dodałem warunek !char.IsUpper(text[i - 1]). Naprawiono błąd, który powodował, że coś takiego jak „AverageNOX” zamieniało się w „Average NO X”, co jest oczywiście błędne, ponieważ powinno brzmieć „Average NOX”.

Niestety nadal ma to błąd, że jeśli masz tekst „FromAStart”, wyjmiesz „From AStart”.

Czy są jakieś przemyślenia na temat rozwiązania tego problemu?


Może coś takiego zadziałałoby: char.IsUpper (tekst [i]) && (char.IsLower (tekst [i - 1]) || (char.IsLower (tekst [i + 1]))
Martin Brown

1
To jest poprawny: if (char.IsUpper(text[i]) && !(char.IsUpper(text[i - 1]) && char.IsUpper(text[i + 1])))Wynik testu: „From Start”, „From THE Start”, „From A Start”, ale musisz i < text.Length - 1w stanie pętli for zignorować ostatni znak i zapobiec wyjątkowi poza zakresem.
CallMeLaNN,

Och, to samo. ! (a && b) i (! a ||! b), ponieważ dolna =! górna.
CallMeLaNN

3

To moje:

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

Czy to ma być C #? Jeśli tak, w jakiej przestrzeni nazw znajduje się lista? Masz na myśli ArrayList lub List <ciąg>?
Martin Brown,

Lista <ciąg> byłaby w porządku. Przepraszam za to.
Cory Foy,

@Martin Zawsze miał poprawną składnię, była po prostu ukryta w <pre><code>code</code></pre>bloku zamiast składni Markdown. Nie musisz go głosować (jeśli to byłeś ty).
George Stocker

3

Upewnij się, że nie są umieszczenie spacji na początku łańcucha, ale umieszczając je pomiędzy kolejnymi literami. Niektóre odpowiedzi tutaj nie dotyczą jednego lub obu tych punktów. Istnieją inne sposoby niż wyrażenia regularne, ale jeśli wolisz z nich korzystać, spróbuj tego:

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

\BJest negowane \b, więc to oznacza nie-słowo-granica. Oznacza to, że wzorzec pasuje do „Y” w XYzabc, ale nie w Yzabclub X Yzabc. Jako niewielki bonus możesz użyć tego na sznurku ze spacjami i nie podwoi ich.


3

Regex umieszcza spację przed każdą wielką literą:

using System.Text.RegularExpressions;

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

Uważaj na miejsce z przodu, jeśli „1 $ 2 $”, to właśnie to zrobi.

Oto wynik:

"This Is A String Without Spaces"

1
Jeśli chcesz, aby liczby również były rozdzielane, użyj tego wzoru regularnego:"([A-Z0-9])([a-z]*)"
Matthias Thomann

2

To, co masz, działa idealnie. Pamiętaj tylko, aby ponownie przypisać valuewartość zwracaną przez tę funkcję.

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

2

Oto jak możesz to zrobić w 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

Inspirowany @MartinBrown, Two Lines of Simple Regex, który rozpozna twoje imię, w tym Acyronimy w dowolnym miejscu ciągu.

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

Podoba mi się to rozwiązanie. Jest krótki i szybki. Jednak, podobnie jak inne rozwiązania, kończy się niepowodzeniem z ciągiem „RegularOTs”. Każde rozwiązanie, które tutaj wypróbowałem, zwraca „Regular O Ts”
Patee Gutee

@PateeGutee OP chciał miejsca przed kapitelami, nie wspomniał o skrótach, mamy poprawkę w dorszie produkcyjnym
Johnny 5

Czy możesz pokazać poprawkę? Mam takie ciągi w moich danych i daje to niepoprawny wynik. Dzięki.
Patee Gutee

@PateeGutee Przepraszam, źle odczytałem, co chciałeś. Pluralizacja to inna kwestia, `RegularOTs ', czego się spodziewasz,„ Regular OTs ”lub„ Regular OTs ”
Johnny 5

1
@PateeGutee Zaktualizowałem moją odpowiedź dla ciebie, uważam, że powinna działać
Johnny 5


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

W Ruby za pośrednictwem Regexp:

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

1
UPS przepraszam. Tęskniłem za tym, że jest to pytanie specyficzne dla C # i opublikowałem tutaj Ruby odpowiedź :(
Artem

1

Wziąłem Kevin Strikers doskonałe rozwiązanie i przeszedłem na VB. Ponieważ jestem zamknięty w .NET 3.5, musiałem również napisać IsNullOrWhiteSpace. To przechodzi wszystkie jego testy.

<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

Pytanie jest nieco stare, ale obecnie w Nuget jest ładna biblioteka, która robi dokładnie to samo, a także wiele innych konwersji na tekst czytelny dla ludzi.

Sprawdź Humanizer na GitHub lub Nuget.

Przykład

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

Właśnie tego spróbowałem i pierwszy link jest teraz zepsuty. NuGet działa, ale pakiet nie kompiluje się w moim rozwiązaniu. Fajny pomysł, jeśli zadziałał.
philw

1

Wydaje się, że to dobra okazja Aggregate. Zostało to zaprojektowane tak, aby było czytelne, niekoniecznie szczególnie szybkie.

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

0

Oprócz odpowiedzi Martina Browna miałem również problem z liczbami. Na przykład: „Lokalizacja 2” lub „Jan22” powinny być odpowiednio „Lokalizacja 2” i „22 stycznia”.

Oto moje wyrażenie regularne do zrobienia tego, używając odpowiedzi Martina Browna:

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

Oto kilka świetnych stron, na których można dowiedzieć się, co oznacza każda część:

Analizator wyrażeń regularnych oparty na Javie (ale działa z większością wyrażeń regularnych .net)

Analizator oparty na skryptach akcji

Powyższe wyrażenie regularne nie będzie działać na stronie skryptu akcji, chyba że zastąpisz wszystkie \p{Ll}z [a-z], \p{Lu}z [A-Z]i \p{Nd}z [0-9].


0

Oto moje rozwiązanie, oparte na sugestii Binary Worriers i budowaniu w komentarzach Richarda Priddysa, ale także biorąc pod uwagę, że w podanym ciągu może istnieć biała przestrzeń, więc nie doda białej przestrzeni obok istniejącej białej przestrzeni.

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

Dla każdego, kto szuka funkcji C ++ odpowiadającej na to samo pytanie, możesz skorzystać z poniższych. Jest to wzorowane na odpowiedzi udzielonej przez @Binary Worrier. Ta metoda automatycznie zachowuje akronimy.

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;

Ciągi testów, których użyłem dla tej funkcji, a wyniki to:

  • „helloWorld” -> „hello World”
  • „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

Rozwiązanie C # dla ciągu wejściowego, który składa się tylko ze znaków ASCII. Wyrażenie regularne zawiera negatywny wygląd, aby zignorować wielką (wielką) literę, która pojawia się na początku łańcucha. Używa Regex.Replace () do zwrócenia pożądanego ciągu.

Zobacz także demo 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);
    }
}

Oczekiwany wynik:

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

Aktualizacja: Oto odmiana, która będzie również obsługiwać akronimy (ciągi wielkich liter).

Zobacz także demo regex101.com i demo ideone.com .

using System;
using System.Text.RegularExpressions;

public class RegexExample
{
    public static void Main()
    {
        var text = "ThisStringHasNoSpacesASCIIButItDoesHaveCapitalsLINQ";

        // Use positive lookbehind to locate all upper-case letters
        // that are preceded by a lower-case letter.
        var patternPart1 = "(?<=[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);
    }
}

Oczekiwany wynik:

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

0

Oto dokładniejsze rozwiązanie, które nie umieszcza spacji przed słowami:

Uwaga: użyłem wielu wyrażeń regularnych (nie jest to zwięzłe, ale będzie również obsługiwać akronimy i słowa jednoliterowe)

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

W :

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

Daje to wynik „Ten ciąg nie ma spacji, ale ma kapitały”
Andy Robinson,

Cześć @AndyRobinson, dzięki. Zmieniłem, aby używać wielu zamienników Regex. Nie jestem pewien, czy istnieje bardziej zwięzły sposób, ale teraz działa.
CrazyTim

0

Wszystkie poprzednie odpowiedzi wyglądały na zbyt skomplikowane.

Miałem ciąg znaków, który miał kombinację wielkich liter i _ użytego, string.Replace (), aby utworzyć _, „” i użyłem następującego, aby dodać spację do wielkich liter.

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

Zainspirowany odpowiedzią Binary Worriera rzuciłem się na to.

Oto wynik:

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

Wykonano test przy użyciu stopera z uruchomionymi 10000000 iteracjami oraz różnymi długościami i kombinacjami łańcuchów.

Średnio 50% (może nieco więcej) szybciej niż odpowiedź 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

Ten zawiera akronimy i liczby mnogie akronimów i jest nieco szybszy niż zaakceptowana odpowiedź:

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

Przechodzi te testy:

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

zaakceptowana odpowiedź dotyczy przypadku, w którym wartość jest zerowa
Chris F Carroll

Dodaje to dodatkowe miejsce przed wyjściem, tj. HireDate => „Data wypożyczenia”. Potrzebuje final.TrimStart lub coś takiego. Myślę, że to właśnie wskazuje jedna z pozostałych odpowiedzi poniżej, ale z powodu zmiany kolejności nie jestem pewien, czy rozmawiał z tobą, ponieważ jego odpowiedź jest oparta na RegEx.
b_levitt,

Dobry chwyt ... powinien był dodać znacznik początku i końca do moich testów ... naprawiony teraz.
Serj Sagan

Podobnie jak w przypadku innych opublikowanych tutaj rozwiązań, nie udaje mu się napis „RegularOTs”. Zwraca „Zwykłe O Ts”
Patee Gutee

Dzięki za pojawienie się liczby mnogiej skrótu, zaktualizowałem również tę opcję.
Serj Sagan

0

Implementacja fold, znana również jako 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());

Oprócz żądania ta implementacja poprawnie zapisuje wiodące, wewnętrzne, końcowe spacje i akronimy, na przykład

" SpacedWord " => " Spaced Word ",  

"Inner Space" => "Inner Space",  

"SomeACRONYM" => "Some ACRONYM".

0

Prosty sposób dodawania spacji po małych i dużych literach lub cyfrach.

    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
Odpowiedzi zawierające tylko kod są odradzane. Kliknij edytuj i dodaj kilka słów podsumowujących, w jaki sposób Twój kod odpowiada na pytanie, lub może wyjaśnij, w jaki sposób Twoja odpowiedź różni się od poprzedniej odpowiedzi / odpowiedzi. Z recenzji
Nick
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.