Учитывая строку «ThisStringHasNoSpacesButItDoesHaveCapitals», как лучше всего добавить пробелы перед заглавными буквами. Таким образом, конечная строка будет выглядеть так: «В этой строке нет пробелов, но есть заглавные буквы».
Вот моя попытка с RegEx
System.Text.RegularExpressions.Regex.Replace(value, "[A-Z]", " $0")
Если регулярное выражение работает, я бы придерживался этого. Regex оптимизирован для обработки строк.
Мне просто любопытно, есть ли лучший или, возможно, даже встроенный подход. Мне даже было бы любопытно увидеть другие подходы к другим языкам.
Ваш код просто не работал, потому что измененная строка является возвращаемым значением функции «Заменить». С помощью этой строки кода: 'System.Text.RegularExpressions.Regex.Replace (значение, «[A-Z]», «$ 0»). Trim ();' это сработало бы отлично. (Просто комментирую, потому что я наткнулся на этот пост, и никто толком не увидел, что не так с вашим кодом.)
Regex.Replace ("ThisStringHasNoSpacesButItDoesHaveCapitals", @ "\ B [A-Z]", m => "" + m);





То, что у вас есть, отлично работает. Просто не забудьте переназначить 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 вы могли бы, но для этого потребовалось бы другое выделение памяти и копия строки. Тем не менее, если производительность вызывает беспокойство, Regex, вероятно, в любом случае не лучший способ.
Могли бы вы также использовать "([^A-Z\\s])([A-Z])", даже с сокращениями?
Вот мой:
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>?
List <string> подойдет. Прости за это.
@Martin У него всегда был правильный синтаксис, он просто был спрятан в блоке <pre><code>code</code></pre> вместо синтаксиса Markdown. Нет необходимости понижать его голос (если это были вы).
Регулярные выражения будут работать нормально (я даже проголосовал за ответ Мартина Брауна), но они дорогие (и лично я считаю, что любой шаблон длиннее пары символов непозволительно тупой)
Эта функция
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]! = '') Если вы повторно запустите приведенный выше код, он будет продолжать добавлять пробелы, это остановит добавление пробелов, если перед заглавной буквой есть пробел. письмо.
Я не уверен, поэтому я подумал, что спрошу, обрабатывает ли этот метод акронимы, как описано в ответе Мартина Брауна «DriveIsSCSICompatible» в идеале стал бы «Drive Is SCSI Compatible»
Это сделало его 1 символом, заменив содержимое вашего оператора for на недавно обновленные операторы if. Может, я что-то делаю не так?
Думаю, да, я просто вставил полную функцию в тестовый проект, и он сработал, извините.
с этим решением «407 ETR Customer Service» преобразуется в «407 ET R Customer Service», а «PAR-MED» преобразуется в «PA R-ME D», оба неверны
"2ND" заменяется на "2 ND", "SPECTER-DVD" заменяется на "SPECTRE-DVD", оба кажутся мне неправильными.
Дорогие? У меня никогда не было регулярных выражений, вызывающих какие-либо проблемы с производительностью. Во многих случаях они быстрее, чем алгоритм длинной формы.
Добавление проверки для char.IsLetter (текст [i + 1]) помогает с акронимами со специальными символами и цифрами (т.е. ABC_DEF не разделяется как AB C_DEF).
Я получаю место перед некоторыми сокращениями. Предлагаю добавить текст [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]! = ''))
Я не уверен, что часть аббревиатур верна, когда она выключена. Я только что запустил тест «ASentenceABC» расширяется до «ASentence A B C». Должно быть "Предложение A B C"
Вы должны сделать это расширение строки this string text
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]))
Это правильный: if (char.IsUpper(text[i]) && !(char.IsUpper(text[i - 1]) && char.IsUpper(text[i + 1]))) Результат теста: «С начала», «С НАЧАЛА», «С НАЧАЛА», но вам нужен i < text.Length - 1 в условии цикла for, чтобы игнорировать последний символ и предотвратить исключение вне допустимого диапазона.
О, это все равно. ! (a && b) и (! a ||! b), потому что lower =! upper.
Производительность не тестировал, но вот в одной строчке с 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. В качестве небольшого бонуса вы можете использовать это в строке с пробелами, и это не удвоит их.
Все эти решения принципиально неверны для современного текста. Вам нужно использовать что-то, что понимает регистр. Поскольку Боб попросил другие языки, я дам пару для 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}])", "");
Хм. Я не уверен, что римские цифры действительно должны считаться прописными в этом примере. Пример модификатора букв в расчет не входит. Если вы зайдете на McDonalds.com, вы увидите, что он написан без пробела.
Также следует отметить, что вы никогда не добьетесь идеального результата. Например, я хотел бы увидеть пример, в котором сортируется «Александр фон Гумбольдт», который должен закончиться как «Александр фон Гумбольдт». Кроме того, есть, конечно, языки, не предназначенные для заглавных и строчных букв.
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})"
Вот пара отличных сайтов, чтобы понять, что означает каждая часть:
Анализатор на основе сценария действий
Приведенное выше регулярное выражение не будет работать на сайте сценария действий, если вы не замените все \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 :(
Вот мое решение, основанное на предложении 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".
Вот как это можно сделать в 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;
Строки тестов, которые я использовал для этой функции, и результаты:
Я знаю, что это старый, но я использую это расширение, когда мне нужно это сделать:
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. Я изменил расширение, чтобы использовать перегрузку SelectMany, которая включает индекс, таким образом избегая первой буквы и ненужных потенциальных накладных расходов на дополнительный вызов TrimStart(' '). Роб.
Я взял отличное решение Кевина Страйкера и преобразовал его в 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 работает, но пакет не компилируется в моем решении. Хорошая идея, если бы это сработало.
Этот включает в себя аббревиатуры и аббревиатуры множественного числа и немного быстрее, чем принятый ответ:
public string Sentencify(string value)
{
if (string.IsNullOrWhiteSpace(value))
return string.Empty;
string final = string.Empty;
for (int i = 0; i < value.Length; i++)
{
if (i != 0 && Char.IsUpper(value[i]))
{
if (!Char.IsUpper(value[i - 1]))
final += " ";
else if (i < (value.Length - 1))
{
if (!Char.IsUpper(value[i + 1]) && !((value.Length >= i && value[i + 1] == 's') ||
(value.Length >= i + 1 && value[i + 1] == 'e' && value[i + 2] == 's')))
final += " ";
}
}
final += value[i];
}
return final;
}
Проходит эти тесты:
string test1 = "RegularOTs";
string test2 = "ThisStringHasNoSpacesASCIIButItDoesHaveCapitalsLINQ";
string test3 = "ThisStringHasNoSpacesButItDoesHaveCapitals";
принятый ответ касается случая, когда значение равно нулю
Это добавляет дополнительный пробел перед выводом, например HireDate => «Дата найма». Нужен финал. TrimStart или что-то в этом роде. Я думаю, что один из других ответов указывает ниже, но из-за переупорядочения я не уверен, говорил ли он с вами, поскольку его ответ основан на RegEx.
Хороший улов ... должен был добавить маркер начала и конца в мои тесты ... теперь исправлено.
Подобно другому решению, размещенному здесь, оно не работает со строкой «RegularOTs». Возвращает "Regular O Ts".
Спасибо за использование аббревиатур во множественном числе, я обновился, чтобы работать и для этого.
Вот более подробное решение, в котором перед словами не ставятся пробелы:
Примечание: Я использовал несколько регулярных выражений (не кратко, но он также обрабатывает аббревиатуры и однобуквенные слова)
Dim s As String = "ThisStringHasNoSpacesButItDoesHaveCapitals"
s = System.Text.RegularExpressions.Regex.Replace(s, "([a-z])([A-Z](?=[A-Z])[a-z]*)", "$1 $2")
s = System.Text.RegularExpressions.Regex.Replace(s, "([A-Z])([A-Z][a-z])", "$1 $2")
s = System.Text.RegularExpressions.Regex.Replace(s, "([a-z])([A-Z][a-z])", "$1 $2")
s = System.Text.RegularExpressions.Regex.Replace(s, "([a-z])([A-Z][a-z])", "$1 $2") // repeat a second time
В:
"ThisStringHasNoSpacesButItDoesHaveCapitals"
"IAmNotAGoat"
"LOLThatsHilarious!"
"ThisIsASMSMessage"
Из:
"This String Has No Spaces But It Does Have Capitals"
"I Am Not A Goat"
"LOL Thats Hilarious!"
"This Is ASMS Message" // (Difficult to handle single letter words when they are next to acronyms.)
Это выводит «В этой строке нет пробелов, но есть заглавные буквы».
Привет, @AndyRobinson, спасибо. Я перешел на использование нескольких замен Regex. Не уверен, что есть более лаконичный способ, но сейчас он работает.
Все предыдущие ответы выглядели слишком сложными.
У меня была строка, в которой использовалась смесь заглавных букв и _, поэтому я использовал 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]*)"
Вдохновленный @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». Каждое решение, которое я пробовал здесь, возвращает "Обычные ошибки"
@PateeGutee, ОП хотел место перед капитолиями, он не упомянул аббревиатуры, у нас есть исправление для этого в продакшене cod
Можешь показать исправление? У меня есть такие строки в моих данных, и это дает мне неверный результат. Спасибо.
@PateeGutee Извините, я неправильно понял то, что вы хотели. Множественное число - это разные вопросы, "RegularOT", чего вы ожидаете от "Обычных ОТ" или "Обычных ОТ"
Я ожидаю чего-то вроде следующего: «Обычные ОТ» -> «Обычные ОТ» ... «Частично просмотреть часто задаваемые вопросы» -> «Частично просмотреть часто задаваемые вопросы»
@PateeGutee Вы ожидаете, что это будет работать только с s, а как насчет "es"? В принципе, каких правил вы ожидаете, если в конце акронима стоит буква «s» или если в конце акрионима стоит одна строчная буква?
@PateeGutee Я обновил свой ответ для вас, я считаю, что это должно сработать
Ваше обновленное решение довольно хорошо обрабатывает множественные аббревиатуры. Однако это не удается со строкой «G7799CertifiedFRs». Я ожидаю «Сертифицированных FR G7799», но ваше обновленное решение дало только «Сертифицированные FR» без «G7799». Ваше исходное решение дало "Сертифицированные F Rs G7799".
Попробуйте заменить [A-Z] на [A-Z1-9]
Попасть туда. "Y2000CertifiedTRs" -> "Y2000Certified TRs", "Y2000TRs" -> "Y2000TRs", в которых отсутствует один пробел после "Y2000".
Следующие тесты прошли успешно: «AdvanceABCs» -> «Advance ABCs», «Advance123s» -> «Advance 123s», «ABCsAdvance ->« ABCs Advance »,« 123sAdvance »->« 123s Advance »
Я удалил правку, так как мое решение было неполным, просто интересно, почему вы используете для этого регулярное выражение? Разве не было бы намного быстрее просто вручную написать функцию, которая разделяется после последней заглавной буквы или цифр?
@PateeGutee, настоящая проблема с регулярным выражением заключается в том, что его сложно понять и сложно поддерживать. Представьте, что через год вам нужно добавить что-то еще, например поддержку множественного числа для es или что-то в этом роде, и это также обеспечивает соблюдение стандарта, согласно которому люди, работающие над кодом, должны знать регулярное выражение. Кажется, у вас уже есть много тестов для этого, почему бы не реализовать некоторый TDD и наметить набор окончательных правил, таких как обработка акцентированных символов и т. д.
Вдохновленный ответом 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;
}
Ответы только на код не приветствуются. Пожалуйста, нажмите редактировать и добавьте несколько слов, описывающих, как ваш код решает вопрос, или, возможно, объясните, чем ваш ответ отличается от предыдущего ответа / ответов. Из обзора
Я обнаружил, что многие из этих ответов довольно тупые, но я не полностью протестировал свое решение, но оно работает для того, что мне нужно, должно обрабатывать аббревиатуры и намного более компактно / читаемо, чем другие 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();
}
Есть ли у вас особые претензии к выбранному вами подходу? Это может помочь нам улучшить ваш метод.