Добавьте пробелы перед заглавными буквами - PullRequest
174 голосов
/ 07 ноября 2008

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

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

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

Ответы [ 30 ]

186 голосов
/ 07 ноября 2008

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

Эта функция

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();
}
134 голосов
/ 07 ноября 2008

В вашем решении есть проблема, заключающаяся в том, что перед первой буквой Т ставится пробел, поэтому вы получаете

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

71 голосов
/ 16 февраля 2011

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

var val = "ThisIsAStringToTest";
val = string.Concat(val.Select(x => Char.IsUpper(x) ? " " + x : x.ToString())).TrimStart(' ');
12 голосов
/ 27 сентября 2013

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

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

8 голосов
/ 01 марта 2011

Добро пожаловать в 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 на покой? Это больше не имеет смысла, и притворяться, что это все, что у тебя есть, просто неправильно. Это делает для плохого кода.

7 голосов
/ 04 октября 2012

Я решил создать простой метод расширения, основанный на коде 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());
4 голосов
/ 24 марта 2009

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

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

Есть мысли о том, как это исправить?

3 голосов
/ 16 февраля 2011

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

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

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

3 голосов
/ 07 ноября 2008

Вот мой:

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()); 
}
2 голосов
/ 15 октября 2012

Вот как вы можете сделать это в 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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...