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

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

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

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

Есть ли у вас особые претензии к выбранному вами подходу? Это может помочь нам улучшить ваш метод.

Blair Conrad 07.11.2008 19:36

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

Michael Meadows 07.11.2008 19:39

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

Bob 07.11.2008 19:51

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

Mattu475 22.04.2015 16:46

Regex.Replace ("ThisStringHasNoSpacesButItDoesHaveCapitals", @ "\ B [A-Z]", m => "" + m);

saquib adil 25.02.2019 16:53
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
204
5
94 115
31
Перейти к ответу Данный вопрос помечен как решенный

Ответы 31

То, что у вас есть, отлично работает. Просто не забудьте переназначить value возвращаемому значению этой функции.

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

У вашего решения есть проблема в том, что оно ставит пробел перед первой буквой 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 Is SCSI Compatible».

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

PandaWood 21.11.2014 06:48

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

Martin Brown 22.11.2014 12:02

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

Ruben9922 09.01.2020 14:55

Вот мой:

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 Brown 08.11.2008 15:31

List <string> подойдет. Прости за это.

Cory Foy 09.11.2008 05:37

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

George Stocker 01.03.2011 00:56
Ответ принят как подходящий

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

Эта функция

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 байт), запуск 100000 преобразований занимает 4517177 тиков для функции, закодированной вручную, а приведенное ниже регулярное выражение занимает 59435719, что позволяет функции с ручным кодированием работать в 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();
}

if (char.IsUpper (text [i]) && text [i - 1]! = '') Если вы повторно запустите приведенный выше код, он будет продолжать добавлять пробелы, это остановит добавление пробелов, если перед заглавной буквой есть пробел. письмо.

Paul Talbot 26.10.2010 12:32

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

Coops 23.07.2013 19:39

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

Coops 23.07.2013 20:36

Думаю, да, я просто вставил полную функцию в тестовый проект, и он сработал, извините.

Binary Worrier 24.07.2013 11:06

с этим решением «407 ETR Customer Service» преобразуется в «407 ET R Customer Service», а «PAR-MED» преобразуется в «PA R-ME D», оба неверны

Julien 26.11.2015 19:40

"2ND" заменяется на "2 ND", "SPECTER-DVD" заменяется на "SPECTRE-DVD", оба кажутся мне неправильными.

Julien 30.08.2016 19:11

Дорогие? У меня никогда не было регулярных выражений, вызывающих какие-либо проблемы с производительностью. Во многих случаях они быстрее, чем алгоритм длинной формы.

Jordan 23.05.2018 18:35

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

HeXanon 03.10.2018 11:33

Я получаю место перед некоторыми сокращениями. Предлагаю добавить текст [i + 1]! = '' В последнюю. if ((text [i - 1]! = '' &&! char.IsUpper (text [i - 1])) || (preserveAcronyms && char.IsUpper (text [i - 1]) && i <text.Length - 1 &&! Char.IsUpper (текст [i + 1]) && text [i + 1]! = ''))

Kishan Choudhary 21.02.2020 12:50

Я не уверен, что часть аббревиатур верна, когда она выключена. Я только что запустил тест «ASentenceABC» расширяется до «ASentence A B C». Должно быть "Предложение A B C"

Tim Rutter 12.04.2020 10:25

Вы должны сделать это расширение строки this string text

Kolob Canyon 05.03.2021 21:04

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» превращалось в «Среднее N O X», что, очевидно, неверно, так как должно читаться «Среднее NOX».

К сожалению, здесь все еще есть ошибка: если у вас есть текст «FromAStart», вы бы получили «From AStart».

Есть мысли по исправлению этого?

Может быть, что-то вроде этого сработает: char.IsUpper (text [i]) && (char.IsLower (text [i - 1]) || (char.IsLower (text [i + 1]))

Martin Brown 22.10.2009 20:52

Это правильный: if (char.IsUpper(text[i]) && !(char.IsUpper(text[i - 1]) && char.IsUpper(text[i + 1]))) Результат теста: «С начала», «С НАЧАЛА», «С НАЧАЛА», но вам нужен i < text.Length - 1 в условии цикла for, чтобы игнорировать последний символ и предотвратить исключение вне допустимого диапазона.

CallMeLaNN 18.03.2011 11:10

О, это все равно. ! (a && b) и (! a ||! b), потому что lower =! upper.

CallMeLaNN 18.03.2011 11:13

Производительность не тестировал, но вот в одной строчке с linq:

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

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

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

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

Добро пожаловать в Unicode

Все эти решения принципиально неверны для современного текста. Вам нужно использовать что-то, что понимает регистр. Поскольку Боб попросил другие языки, я дам пару для Perl.

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

Testing TheLoneRanger
               Worst:    The_Lone_Ranger
               Ok:       The_Lone_Ranger
               Better:   The_Lone_Ranger
               Best:     The_Lone_Ranger
Testing MountMᶜKinleyNationalPark
     [WRONG]   Worst:    Mount_MᶜKinley_National_Park
     [WRONG]   Ok:       Mount_MᶜKinley_National_Park
     [WRONG]   Better:   Mount_MᶜKinley_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              ,
    $MountMᶜKinleyNationalPark  ,
    $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
    MountMᶜKinleyNationalPark
    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‌}])", "");

Randyaa 30.06.2011 18:18

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

Martin Brown 12.03.2012 15:47

Также следует отметить, что вы никогда не добьетесь идеального результата. Например, я хотел бы увидеть пример, в котором сортируется «Александр фон Гумбольдт», который должен закончиться как «Александр фон Гумбольдт». Кроме того, есть, конечно, языки, не предназначенные для заглавных и строчных букв.

Martin Brown 12.03.2012 16:07
replaceAll("(?<=[^^\\p{Uppercase}])(?=[\\p{Uppercase}])"," ");

Помимо ответа Мартина Брауна, у меня также была проблема с числами. Например: «Местоположение2» или «22 января» должно быть «Местоположение 2» и «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].

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

В Ruby через Regexp:

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

Ой, извините. Я пропустил этот вопрос, относящийся к C#, и разместил здесь ответ Ruby :(

Artem 27.07.2012 00:29

Вот мое решение, основанное на предложении 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();
    }

Я намеревался создать простой метод расширения, основанный на коде 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 29.01.2018 22:33

Вот как это можно сделать в 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

Для тех, кто ищет функцию 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" -> "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"
  • «А» -> «А»

Я знаю, что это старый, но я использую это расширение, когда мне нужно это сделать:

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.2015 13:05

Спасибо @ user1069816. Я изменил расширение, чтобы использовать перегрузку SelectMany, которая включает индекс, таким образом избегая первой буквы и ненужных потенциальных накладных расходов на дополнительный вызов TrimStart(' '). Роб.

Rob Hardy 25.06.2015 16:45

Я взял отличное решение Кевина Страйкера и преобразовал его в 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

Решение 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]

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

Проверьте Гуманизатор на 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 27.11.2014 16:50

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

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

принятый ответ касается случая, когда значение равно нулю

Chris F Carroll 15.12.2014 12:59

Это добавляет дополнительный пробел перед выводом, например HireDate => «Дата найма». Нужен финал. TrimStart или что-то в этом роде. Я думаю, что один из других ответов указывает ниже, но из-за переупорядочения я не уверен, говорил ли он с вами, поскольку его ответ основан на RegEx.

b_levitt 07.02.2015 01:52

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

Serj Sagan 09.02.2015 20:53

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

Patee Gutee 29.01.2018 22:33

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

Serj Sagan 29.01.2018 23:50

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

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

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

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

Andy Robinson 17.04.2015 19:51

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

CrazyTim 21.04.2015 07:36

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

У меня была строка, в которой использовалась смесь заглавных букв и _, поэтому я использовал 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.
        }
    }
}

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

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"

Если вы хотите, чтобы числа также разделялись, используйте вместо этого этот шаблон регулярного выражения: "([A-Z0-9])([a-z]*)"

Matthias Thomann 18.02.2016 19:00

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

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». Каждое решение, которое я пробовал здесь, возвращает "Обычные ошибки"

Patee Gutee 29.01.2018 22:30

@PateeGutee, ОП хотел место перед капитолиями, он не упомянул аббревиатуры, у нас есть исправление для этого в продакшене cod

johnny 5 29.01.2018 22:35

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

Patee Gutee 29.01.2018 22:43

@PateeGutee Извините, я неправильно понял то, что вы хотели. Множественное число - это разные вопросы, "RegularOT", чего вы ожидаете от "Обычных ОТ" или "Обычных ОТ"

johnny 5 30.01.2018 05:04

Я ожидаю чего-то вроде следующего: «Обычные ОТ» -> «Обычные ОТ» ... «Частично просмотреть часто задаваемые вопросы» -> «Частично просмотреть часто задаваемые вопросы»

Patee Gutee 31.01.2018 17:53

@PateeGutee Вы ожидаете, что это будет работать только с s, а как насчет "es"? В принципе, каких правил вы ожидаете, если в конце акронима стоит буква «s» или если в конце акрионима стоит одна строчная буква?

johnny 5 31.01.2018 20:59

@PateeGutee Я обновил свой ответ для вас, я считаю, что это должно сработать

johnny 5 31.01.2018 22:35

Ваше обновленное решение довольно хорошо обрабатывает множественные аббревиатуры. Однако это не удается со строкой «G7799CertifiedFRs». Я ожидаю «Сертифицированных FR G7799», но ваше обновленное решение дало только «Сертифицированные FR» без «G7799». Ваше исходное решение дало "Сертифицированные F Rs G7799".

Patee Gutee 01.02.2018 20:35

Попробуйте заменить [A-Z] на [A-Z1-9]

johnny 5 01.02.2018 21:25

Попасть туда. "Y2000CertifiedTRs" -> "Y2000Certified TRs", "Y2000TRs" -> "Y2000TRs", в которых отсутствует один пробел после "Y2000".

Patee Gutee 01.02.2018 23:23

Следующие тесты прошли успешно: «AdvanceABCs» -> «Advance ABCs», «Advance123s» -> «Advance 123s», «ABCsAdvance ->« ABCs Advance »,« 123sAdvance »->« 123s Advance »

Patee Gutee 01.02.2018 23:23

Я удалил правку, так как мое решение было неполным, просто интересно, почему вы используете для этого регулярное выражение? Разве не было бы намного быстрее просто вручную написать функцию, которая разделяется после последней заглавной буквы или цифр?

johnny 5 01.02.2018 23:33

@PateeGutee, настоящая проблема с регулярным выражением заключается в том, что его сложно понять и сложно поддерживать. Представьте, что через год вам нужно добавить что-то еще, например поддержку множественного числа для es или что-то в этом роде, и это также обеспечивает соблюдение стандарта, согласно которому люди, работающие над кодом, должны знать регулярное выражение. Кажется, у вас уже есть много тестов для этого, почему бы не реализовать некоторый TDD и наметить набор окончательных правил, таких как обработка акцентированных символов и т. д.

johnny 5 02.02.2018 00:55

Вдохновленный ответом 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.

Похоже, хорошая возможность для Aggregate. Это сделано для того, чтобы его можно было читать, но не обязательно очень быстро.

someString
.Aggregate(
   new StringBuilder(),
   (str, ch) => {
      if (char.IsUpper(ch) && str.Length > 0)
         str.Append(" ");
      str.Append(ch);
      return str;
   }
).ToString();
    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;
    }

Реализация с 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".

Простой способ добавить пробелы после букв нижнего регистра, букв верхнего регистра или цифр.

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

Ответы только на код не приветствуются. Пожалуйста, нажмите редактировать и добавьте несколько слов, описывающих, как ваш код решает вопрос, или, возможно, объясните, чем ваш ответ отличается от предыдущего ответа / ответов. Из обзора

Nick 08.04.2019 04:37

Я обнаружил, что многие из этих ответов довольно тупые, но я не полностью протестировал свое решение, но оно работает для того, что мне нужно, должно обрабатывать аббревиатуры и намного более компактно / читаемо, чем другие IMO:

private string CamelCaseToSpaces(string s)
    {
        if (string.IsNullOrEmpty(s)) return string.Empty;

        StringBuilder stringBuilder = new StringBuilder();
        for (int i = 0; i < s.Length; i++)
        {
            stringBuilder.Append(s[i]);

            int nextChar = i + 1;
            if (nextChar < s.Length && char.IsUpper(s[nextChar]) && !char.IsUpper(s[i]))
            {
                stringBuilder.Append(" ");
            }
        }

        return stringBuilder.ToString();
    }

Другие вопросы по теме