Додайте пробіли перед великими літерами


194

З огляду на рядок "ThisStringHasNoSpacesButItDoesHaveCapitals", який найкращий спосіб додати пробіли перед великими літерами. Таким чином, кінцевим рядком буде "Ця струна не має пробілів, але вона має великі літери"

Ось моя спроба з RegEx

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

2
Чи є у вас певна скарга на підхід, який ви застосували? Це може допомогти нам покращити ваш метод.
Блер Конрад

Якщо регекс працює, то я б дотримувався цього. Regex застосовується для маніпуляцій зі струнами.
Майкл Медоуз

Мені просто цікаво, чи є кращий або, можливо, навіть побудований підхід. Мені навіть цікаво побачити інші підходи з іншими мовами.
Боб

2
Ваш код просто не працював, оскільки модифікована рядок є значенням повернення функції "Замінити". З цим рядком коду: 'System.Text.RegularExpressions.Regex.Replace (значення, "[AZ]", "$ 0"). Trim ();' це б справно працювало. (Просто коментуючи, тому що я наткнувся на цю посаду, і ніхто насправді не побачив, що не так з вашим кодом.)
Mattu475,

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

Відповіді:


203

Режекси будуть добре працювати (я навіть проголосував за відповідь Мартіна Браунаса), але вони дорогі (і особисто я знаходжу будь-який візерунок довше, ніж пара символів непомітно тупим)

Ця функція

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

Зробить це 100 000 разів за 2968,750 кліщів, регулярний вимір візьме 25 000 000 кліщів (і це зі складеним регулярним виразом).

Краще, для заданого значення краще (тобто швидше), проте це більше код для підтримки. "Краще" часто є компромісом конкуруючих вимог.

Сподіваюся, це допомагає :)

Оновлення
Це досить давно, оскільки я переглянув це, і я просто зрозумів, що терміни не оновлювалися з моменту зміни коду (він лише трохи змінився).

У рядку з повторенням "Abbbbbbbbb" 100 разів (тобто 1000 байт) запуск 100 000 перетворень приймає вручну закодовану функцію 4,517,177 тиків, а Regex нижче займає 59,435,719, змушуючи кодовану функцію вручну виконувати в 7,6% часу, який займає Регекс.

Оновлення 2 Чи будуть враховані абревіатури? Це буде зараз! Логіка параметра if якщо досить незрозуміла, оскільки ви можете розширити її до цього ...

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

... зовсім не допомагає!

Ось оригінальний простий метод, який не хвилює акроніми

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 (текст [i]) && text [i - 1]! = '') Якщо ви повторно запустите код над ним, він продовжує додавати пробіли, це зупинить додавання пробілів, якщо перед капіталом є пробіл лист.
Пол Талбот

Я не впевнений, тому я подумав, що запитаю, чи обробляє цей метод абревіатури, як описано у відповіді Мартіна Брауна "DriveIsSCSICompatible" в ідеалі став би "Drive Is SCSI сумісний"
Coops

Це зробило його 1 символом, замінивши вміст вашої заяви на нещодавно оновлене, якщо у твердженнях я можу зробити щось не так?
Купи

1
Додавання чека на char.IsLetter (текст [i + 1]) допомагає з абревіатурами зі спеціальними символами та цифрами (тобто ABC_DEF не буде розбиватися як AB C_DEF).
HeXanon

1
Я не впевнений, що частина акронімів правильна, коли її вимкнено. Я щойно провів тест "ASentenceABC" розширюється на "ASentence AB C". Повинен бути "A Sentence AB C"
Тім Раттер

150

У вашому рішенні є проблема в тому, що він ставить пробіл перед першою літерою T, щоб ви отримали

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

Щоб обійти цю проблему, знайдіть і малу літеру, яка передує їй, а потім вставте пробіл посередині:

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

Редагувати 1:

Якщо ви користуєтесь @"(\p{Ll})(\p{Lu})"ним, ви також підбираєте наголошені символи.

Редагувати 2:

Якщо ваші рядки можуть містити абревіатури, ви можете скористатися цим:

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

Тож "DriveIsSCSICompatible" перетворюється на "Диск сумісний з SCSI"


3
Не могли ви також просто зберегти результат оригінальних RegEx та Trim ()?
PandaWood

3
@PandaWood ви можете, але це потребує іншого розподілу пам'яті та копіювання рядка. Однак, якщо продуктивність хвилює, Regex - це, мабуть, не найкращий спосіб.
Мартін Браун

Чи можете ви також вживати "([^A-Z\\s])([A-Z])", навіть із абревіатурами?
Рубен9922

82

Не перевіряли продуктивність, але тут, в одному рядку з linq:

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

18

Я знаю, що це стара, але це розширення, яке я використовую, коли мені потрібно це зробити:

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

Це дозволить вам використовувати MyCasedString.ToSentence()


Мені подобається ідея цього як методу розширення, якщо ви додасте TrimStart(' ')його, вилучите провідний простір.
користувач1069816

1
Дякуємо @ user1069816 Я змінив розширення, щоб використовувати перевантаження, SelectManyяке включає індекс, таким чином воно дозволяє уникнути першої літери та непотрібних накладних накладних витрат додаткового виклику TrimStart(' '). Роб.
Роб Харді

9

Я вирішив зробити простий метод розширення на основі коду Binary Worrier, який буде правильно обробляти абревіатури та є повторюваним (не буде маніпулювати вже рознесеними словами). Ось мій результат.

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

Ось одиничні тестові випадки, які ця функція проходить. Я додав до цього списку більшість запропонованих випадків, що стосуються tchrist. Три з них, які не проходять (дві - просто римські цифри), коментуються:

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

Як і в іншому розміщеному тут рішенні, воно не працює із рядком "RegularOTs". Повертається "Regular O Ts"
Patee Gutee

8

Ласкаво просимо до Unicode

Усі ці рішення по суті неправильні для сучасного тексту. Вам потрібно використовувати щось, що розуміє випадок. Оскільки Боб попросив інших мов, я дам пару за Perl.

Я пропоную чотири рішення - від найгіршого до кращого. Тільки найкращий завжди правий. У інших є проблеми. Ось тестовий пробіг, щоб показати вам, що працює, а що ні, і де. Я використав підкреслення, щоб ви могли бачити, де пробіли, і я позначив як неправильне все, що є, ну, неправильно.

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

До речі, майже кожен тут обрав перший шлях, той із позначкою "Найгірше". Кілька з них вибрали другий спосіб, позначений «ОК». Але ще ніхто до мене не показав вам, як робити "кращий" або "найкращий" підхід.

Ось програма тестування з чотирма методами:

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

Коли ви зможете оцінити так само, як "Найкраще" на цьому наборі даних, ви будете знати, що все зробили правильно. До цього ви цього не зробили. Ніхто інший тут не зробив краще, ніж "Ок", і більшість зробили це "Найгірше". Я з нетерпінням чекаю, коли хтось розмістить правильний ℂ♯ код.

Я зауважую, що код виділення StackOverflow знову жалібно стеопід. Вони роблять все ті самі старі кульгаві, що і (більшість, але не всі) з решти згаданих тут бідних підходів. Чи не давно минулий час відпочити ASCII на відпочинок? Це більше не має сенсу, і робити вигляд, що все, що у вас є, просто неправильно. Це робить поганий код.


Ваша відповідь "Найкраща" здається найближчою поки що, але, схоже, вона не сприймає розділові знаки пунктуації або інші літери, що не мають малих літер. Це, здається, найкраще працює для мене (у Java): substituAll ("(? <= [^^ \\ p {javaUpperCase}]) (? = [\\ p {javaUpperCase}])", "");
Randyaa

Хм. Я не впевнений, що в цьому прикладі римські цифри дійсно повинні вважатися великими літерами. Приклад модифікатора літер, безумовно, не слід вважати. Якщо ви перейдете на McDonalds.com, ви побачите, що це написано без пробілу.
Мартін Браун

Слід також зазначити, що ви ніколи не отримаєте це ідеальним. Наприклад, я хотів би побачити приклад, який розбирає "AlexandervonHumboldt", який повинен закінчуватися як "Alexander von Humboldt". Тоді, звичайно, є мови, які не мають призначення капіталу та нижнього регістру.
Мартін Браун

4

Binary Worrier, я використав ваш запропонований код, і він досить хороший, у мене є лише одне незначне доповнення до нього:

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

Я додав умову !char.IsUpper(text[i - 1]) . Це виправило помилку, яка може призвести до того, що щось на зразок "AverageNOX" перетвориться на "Середній NO X", що, очевидно, неправильно, оскільки він повинен читати "Середній NOX".

На жаль, у цьому все ще є помилка, що якщо у вас є текст "FromAStart", ви отримаєте "From AStart".

Будь-які думки щодо виправлення цього?


Можливо, щось подібне спрацювало б: char.IsUpper (текст [i]) && (char.IsLower (текст [i - 1]) || (char.IsLower (текст [i + 1]))
Мартін Браун,

1
Це правильний: if (char.IsUpper(text[i]) && !(char.IsUpper(text[i - 1]) && char.IsUpper(text[i + 1])))Результат тесту: "З початку", "З початку", "з початку", але вам потрібно i < text.Length - 1в умові for циклу ігнорувати останній символ та запобігати виключенню поза діапазоном.
CallMeLaNN

О, так само. ! (a&& b) і (! a ||! b), тому що нижній =! верхній.
CallMeLaNN

3

Ось моя:

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

Це повинно бути C #? Якщо так, у якому просторі імен є Список? Ви маєте на увазі ArrayList або List <string>?
Мартін Браун

Список <string> буде добре. Вибач за це.
Cory Foy

@Martin У нього завжди був правильний синтаксис, він був просто захований у <pre><code>code</code></pre>блоці замість синтаксису Markdown. Не потрібно брати на нього відповідальність (якщо це були ви).
Джордж Стокер

3

Переконайтеся , що ви НЕ покласти прогалини на початку рядка, але які поміщаючи їх між послідовними столицями. Деякі відповіді тут не стосуються одного або обох цих питань. Є інші способи, ніж регулярний вираз, але якщо ви вважаєте за краще використовувати це, спробуйте це:

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

Значення \Bзаперечується \b, тому воно не означає межі слова. Це означає, що шаблон відповідає "Y" в XYzabc, але не в Yzabcабо X Yzabc. Як невеликий бонус, ви можете використовувати це на рядку з пробілами в ньому, і це не подвоїть їх.


3

Цей Regex розміщує пробіл перед кожною великою літерою:

using System.Text.RegularExpressions;

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

Зважайте на простір спереду, якщо "$ 1 $ 2", саме це і зробить.

Це результат:

"This Is A String Without Spaces"

1
Якщо ви хочете, щоб цифри також були відокремлені, скористайтеся цією схемою регулярного вираження:"([A-Z0-9])([a-z]*)"
Matthias Thomann

2

Те, що у вас є, працює чудово. Просто пам’ятайте про перепризначення valueзворотного значення цієї функції.

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

2

Ось як ви могли це зробити в 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

Натхненний на @MartinBrown, Двома рядками простого Regex, які дозволять вирішити ваше ім’я, включаючи ацироніми в будь-якому місці рядка.

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

Мені подобається це рішення. Це коротко і швидко. Однак, подібно до інших рішень, він не вдається зі строкою "RegularOTs". Кожне рішення, яке я спробував тут, повертає "Regular O Ts"
Patee Gutee

@PateeGutee ОР хотів місця перед капітоліями, він не згадав абревіатури, у нас є виправлення для цього у виробництві cod
johnny 5

Чи можете ви показати виправлення? У моїх даних є такі рядки, і це дає невірний результат. Дякую.
Patee Gutee

@PateeGutee Вибачте, я неправильно прочитав те, що ви хотіли. Плюралізація - це різні питання, "RegularOTs", що ви очікуєте, що трапиться "Регулярні ОТ" або "Регулярні ОТ",
Джоні 5

1
@PateeGutee Я оновив свою відповідь за вас, я вважаю, що це має спрацювати
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

У Ruby, через Regexp:

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

1
На жаль, вибачте. Я пропустив, що це специфічне запитання на C # і опублікував тут Рубі відповідь :(
Артем

1

Я прийняв відмінне рішення Kevin Strikers і перейшов на VB. Оскільки я заблокований у .NET 3.5, мені також довелося написати IsNullOrWhiteSpace. Це проходить усі його випробування.

<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

Питання трохи застаріле, але на сьогоднішній день у Nuget є приємна бібліотека, яка робить саме це, як і багато інших перетворень на текст, читаний людиною.

Перевірте Humanizer на GitHub або Nuget.

Приклад

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

Щойно спробував це, і перше посилання зараз розірвано. NuGet працює, але пакет не компілюється в моєму рішенні. Гарна ідея, якби це спрацювало.
philw

1

Здається, це гарна нагода для Aggregate. Це розроблено для читання, не обов'язково особливо швидкого.

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

0

Окрім відповіді Мартіна Брауна, у мене була проблема і з номерами. Наприклад: "Location2" або "Jan22" має бути відповідно до "Location 2" та "22 січня".

Ось мій регулярний вираз для цього, використовуючи відповідь Мартина Брауна:

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

Ось пара чудових веб-сайтів для з'ясування того, що означає кожна частина:

Аналізатор регулярних виразів на основі Java (але працює для більшості .net regex)

Аналізатор на основі сценаріїв дій

Вище регулярний вираз не працюватиме на місці сценарію дії , якщо не замінити всі \p{Ll}з [a-z], то \p{Lu}з [A-Z], і \p{Nd}з [0-9].


0

Ось моє рішення, яке базується на пропозиціях Бінарних завад і на основі коментарів Річарда Пріддіса, але також враховуючи, що білий простір може існувати в наданій рядку, тому він не додасть пробілу поруч із наявним пробілом.

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

Для всіх, хто шукає функцію C ++, яка відповідає на це ж питання, можна скористатися наступним. Це моделюється після відповіді, наданої @Binary Worrier. Цей метод просто зберігає абревіатури автоматично.

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;

Я використовував для цієї функції рядки тестів, і результати:

  • "helloWorld" -> "привіт світ"
  • "HelloWorld" -> "Hello World"
  • "HelloABCWorld" -> "Привіт ABC World"
  • "HelloWorldABC" -> "Hello World ABC"
  • "ABCHelloWorld" -> "ABC Hello World"
  • "СВІТ ABC HELLO" -> "СВІТ ABC HELLO"
  • "ABCHELLOWORLD" -> "ABCHELLOWORLD"
  • "А" -> "А"

0

Рішення C # для вхідного рядка, що складається лише з символів ASCII. Регулярний вираз включає в себе негативну ' назад , щоб ігнорувати великої (верхній регістр) лист , яке з'являється на початку рядка. Використовує Regex.Replace () для повернення потрібного рядка.

Також дивіться 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);
    }
}

Очікуваний вихід:

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

Оновлення: Ось варіант, який також буде обробляти абревіатури (послідовності малих літер).

Дивіться також демонстрацію regex101.com та демонстрацію 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);
    }
}

Очікуваний вихід:

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

0

Ось більш ретельне рішення, яке не ставить пробіли перед словами:

Примітка: я використовував кілька Regexs (не стислий, але він також буде обробляти абревіатури та слова з однієї літери)

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

В :

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

Вийшов :

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

Це виводить "Ця струна не має просторів, але це має капітали"
Енді Робінсон,

Привіт @AndyRobinson, дякую Я змінив використання декількох замінників Regex. Не впевнений, чи є більш стислий спосіб, але він працює зараз.
CrazyTim

0

Усі попередні відповіді виглядали надто складними.

У мене був рядок, який містив суміш великих літер і _, що використовується, string.Замініть (), щоб зробити _, "" і використовував наступне, щоб додати пробіл у великих літерах.

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

Натхненний відповіддю Бінарного страшника, я на цьому помахнувся.

Ось результат:

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

Зробив тест, використовуючи секундомір із виконанням 10000000 ітерацій та різних довжин рядків та комбінацій.

В середньому на 50% (можливо, трохи більше) швидше, ніж відповідь Бінарного Воррі.


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

Це включає абревіатури та множини абревіатури і трохи швидше, ніж прийнята відповідь:

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

Проходить ці тести:

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

прийнята відповідь стосується випадку, коли значення є нульовим
Кріс Ф Керролл

Це додає додаткового місця перед висновком, тобто HireDate => "Дата найму". Потрібен остаточний.TrimStart або щось подібне. Я думаю, що саме на це вказується одна з інших відповідей нижче, але через переупорядкування я не впевнений, чи розмовляв він з вами, оскільки його відповідь заснована на RegEx.
b_levitt

Хороший улов ... мав би додати маркер початку та кінця до моїх тестів ... виправлений зараз.
Serj Sagan

Як і в іншому розміщеному тут рішенні, воно не працює із рядком "RegularOTs". Повертається "Regular O Ts"
Patee Gutee

Дякуємо, що вивели множини абревіатури, я також оновив роботу над цим.
Serj Sagan

0

Реалізація fold, також відома як 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());

Окрім запиту, ця реалізація правильно зберігає провідні, внутрішні, кінцеві пробіли та абревіатури, наприклад,

" SpacedWord " => " Spaced Word ",  

"Inner Space" => "Inner Space",  

"SomeACRONYM" => "Some ACRONYM".

0

Простий спосіб додавання пробілів після малих літер, великих літер або цифр.

    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
Відповіді, що стосуються лише коду, не враховують. Клацніть по редагуванню та додайте кілька слів, узагальнюючи те, як ваш код вирішує питання, або, можливо, поясніть, чим ваша відповідь відрізняється від попередньої відповіді / відповіді. З огляду
Нік
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.