Добавьте пробелы перед заглавными буквами


194

Учитывая строку «ThisStringHasNoSpacesButItDoesHaveCapitals», что является лучшим способом добавить пробелы перед заглавными буквами. Таким образом, конечная строка будет такой: «В этой строке нет пробелов, но есть заглавные буквы»

Вот моя попытка с RegEx

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

2
Есть ли у вас особая жалоба на выбранный вами подход? Это может помочь нам улучшить ваш метод.
Блэр Конрад

Если регулярное выражение работает, то я буду придерживаться этого. Regex оптимизирован для работы со строками.
Майкл Медоус

Мне просто любопытно, есть ли лучший или, возможно, даже встроенный подход. Мне даже было бы любопытно увидеть другие подходы с другими языками.
Боб

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

Regex.Replace ("ThisStringHasNoSpacesButItDoesHaveCapitals", @ "\ B [AZ]", m => "" + m);
Сакиб Адил

Ответы:


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 раз за 2 968 750 тиков, регулярное выражение займет 25 000 000 тиков (и это с скомпилированным регулярным выражением).

Это лучше, для данного значения лучше (то есть быстрее), однако, это больше кода для поддержки. «Лучше» часто является компромиссом конкурирующих требований.

Надеюсь это поможет :)

Обновление
Прошло довольно много времени с тех пор, как я посмотрел на это, и я просто понял, что время не обновлялось, так как код изменился (он только немного изменился).

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

Обновление 2 Будут ли приняты во внимание Сокращения? Это будет сейчас! Логика if 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 (text [i]) && text [i - 1]! = '') Если вы перезапустите приведенный выше код, он продолжит добавлять пробелы, это остановит добавление пробелов, если перед заглавной есть пробел письмо.
Пол Тэлбот

Я не уверен, поэтому я подумал, что задам вопрос: обрабатывает ли этот метод аббревиатуры, описанные в ответе Мартина Брауна «DriveIsSCSICompatible», в идеале «Drive is SCSI Compatible»
Coops

Это сделало его 1 символом, заменив содержимое вашего оператора for на недавно обновленные операторы if, возможно, я что-то не так делаю?
Курятник

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

1
Я не уверен, что часть аббревиатур верна, когда она выключена. Я только что выполнил тест "ASentenceABC" расширяется до "ASentence AB C". Должно быть "Приговор 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» становится «Drive SCSI-совместимым»


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

3
@PandaWood вы могли бы, но это потребовало бы другого выделения памяти и копирования строк. Тем не менее, если производительность вызывает беспокойство, Regex, вероятно, не лучший путь.
Мартин Браун,

Не могли бы вы также использовать "([^A-Z\\s])([A-Z])", даже с аббревиатурами?
Ruben9922

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(' ')его, то удалит начальный пробел.
user1069816 22.06.15

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

Вот примеры модульных тестов, которые эта функция проходит. Я добавил большинство предложенных дел Криста в этот список. Три из тех, что он не проходит (два просто римские цифры), закомментированы:

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

Добро пожаловать в Юникод

Все эти решения по существу не соответствуют современному тексту. Вам нужно использовать что-то, что понимает дела. Так как Боб попросил другие языки, я дам пару для 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): replaceAll ("(? <= [^^ \\ p {javaUpperCase}]) (? = [\\ p {javaUpperCase}])", "");
Рандя

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

Следует также отметить, что вы никогда не получите это, чтобы быть идеальным. Например, я хотел бы видеть пример, который сортирует "AlexandervonHumboldt", который должен закончиться как "Александр фон Гумбольдт". Тогда, конечно, есть языки, которые не имеют прописных и строчных букв.
Мартин Браун,

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 (text [i]) && (char.IsLower (text [i - 1]) || (char.IsLower (text [i + 1])))
Martin Brown

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 #? Если да, то в каком пространстве имен находится List? Вы имеете в виду ArrayList или List <string>?
Мартин Браун

Список <строка> будет хорошо. Извини за это.
Кори Фой

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

3

Убедитесь , что вы не положить пробелы в начале строки, но которые помещая их между последовательными столицами. Некоторые из ответов здесь не касаются одного или обоих из этих пунктов. Есть и другие способы, чем регулярное выражение, но если вы предпочитаете использовать это, попробуйте это:

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

Знак \Bявляется отрицательным \b, поэтому он представляет собой несловесную границу. Это означает, что шаблон соответствует «Y» в XYzabc, но не в Yzabcили X Yzabc. В качестве небольшого бонуса, вы можете использовать это на строке с пробелами, и она не удвоит их.


3

Это регулярное выражение помещает пробел перед каждой заглавной буквой:

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, Two Lines of Simple 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 ОП хотел место перед капитолиями, он не упомянул аббревиатуры, у нас есть исправление для производственной трески
Джонни 5

Вы можете показать исправление? У меня есть такие строки в моих данных, и это дает мне неверный результат. Спасибо.
Пати Гути

@PateeGutee Извините, я неправильно понял, что вы хотели. Плюрализация - это разные вопросы, «RegularOTs», что вы ожидаете, произойдет «Regular OTs» или «Regular OT s»
Джонни 5

1
@PateeGutee Я обновил свой ответ для вас, я считаю, что это должно сработать
Джонни 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 # вопрос и разместил здесь ответ Ruby :(
Артем

1

Я взял отличное решение Кевина Страйкера и перешел на 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» и «Jan 22» соответственно.

Вот мое регулярное выражение для этого, используя ответ Мартина Брауна:

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

Вот пара отличных сайтов, чтобы понять, что значит каждая часть:

Анализатор регулярных выражений на основе Java (но работает для большинства .net регулярных выражений)

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

Выше регулярное выражение не будет работать на месте сценария действия , если не заменить все \p{Ll}с [a-z], то \p{Lu}с [A-Z], и \p{Nd}с [0-9].


0

Вот мое решение, основанное на предложении Binary Worriers и построенном в комментариях Ричарда Приддиса, но также с учетом того, что в предоставленной строке может существовать пробел, поэтому он не добавляет пробел рядом с существующим пробелом.

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" -> "Hello ABC World"
  • "HelloWorldABC" -> "Hello World ABC"
  • "ABCHelloWorld" -> "ABC Hello World"
  • "ABC HELLO WORLD" -> "ABC HELLO WORLD"
  • "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

Вот более полное решение, которое не ставит пробелы перед словами:

Примечание: я использовал несколько регулярных выражений (не кратко, но он также будет обрабатывать аббревиатуры и однобуквенные слова)

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"

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

Это выводит: «У этой строки нет пробелов, но есть заглавные буквы»
Энди Робинсон

Привет @AndyRobinson, спасибо. Я изменил, чтобы использовать несколько замен Regex. Не уверен, что есть более краткий способ, но он работает сейчас.
CrazyTim

0

Все предыдущие ответы выглядели слишком сложными.

У меня была строка, состоящая из прописных букв и символа _, поэтому я использовал string.Replace (), чтобы сделать _, "", и использовал следующее для добавления пробела в заглавные буквы.

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

Вдохновленный ответом Binary Worrier, я об этом подумал.

Вот результат:

/// <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% (может быть, чуть больше) быстрее, чем ответ 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

Этот включает в себя сокращения и множественное число аббревиатур и немного быстрее, чем принятый ответ:

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 => «Hire Date». Необходим финал. Тримстарт или что-то в этом роде. Я думаю, что это то, на что указывает один из других ответов ниже, но из-за изменения порядка я не уверен, говорил ли он с вами, так как его ответ основан на RegEx.
b_levitt

Хороший улов ... надо было добавить маркер начала и конца в мои тесты ... исправлено.
Серж Саган,

Подобно другому решению, опубликованному здесь, оно завершается ошибкой со строкой "RegularOTs". Возвращает "Regular O Ts"
Patee Gutee

Спасибо за то, что вывели множественное число аббревиатур, я обновился, чтобы работать и для этого.
Серж Саган

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.