Как перейти от этой строки: "ThisIsMyCapsDelimitedString"
... к этой строке: "This Is My Caps Delimited String"
Желательно минимальное количество строк кода на VB.net, но приветствуется и C#.
Ваше здоровье!
Это будет только ограниченное использование. В основном я буду использовать его для анализа имен переменных, таких как ThisIsMySpecialVariable,
Это сработало для меня: Regex.Replace(s, "([A-Z0-9]+)", " ").Trim(). А если вы хотите разделить каждую заглавную букву, просто уберите плюс.





Вероятно, есть более элегантное решение, но вот что я придумал сразу:
string myString = "ThisIsMyCapsDelimitedString";
for (int i = 1; i < myString.Length; i++)
{
if (myString[i].ToString().ToUpper() == myString[i].ToString())
{
myString = myString.Insert(i, " ");
i++;
}
}
Замечательный комментарий Гранта Вагнера в сторону:
Dim s As String = RegularExpressions.Regex.Replace("ThisIsMyCapsDelimitedString", "([A-Z])", " ")
Хороший замечание ... Не стесняйтесь вставлять .substring (), .trimstart (), .trim (), .remove () и т. д. По вашему выбору. :)
string s = "ThisIsMyCapsDelimitedString";
string t = Regex.Replace(s, "([A-Z])", " ").Substring(1);
Я знал, что будет простой способ RegEx ... Мне нужно больше его использовать.
Не гуру регулярных выражений, но что происходит с "HeresAWTFString"?
Вы получаете «Heres A W T F String», но именно это Матиас Нино просил в своем вопросе.
Да, ему нужно добавить, что «несколько соседних столиц не трогают». Что довольно очевидно во многих случаях, например, "PublisherID" здесь идет к "Publisher I D", что ужасно.
Regex.Replace("ThisIsMyCapsDelimitedString", "(\B[A-Z])", " ")
На данный момент это лучшее решение, но вам нужно использовать \\ B для компиляции. В противном случае компилятор пытается рассматривать \ B как escape-последовательность.
Хорошее решение. Может ли кто-нибудь придумать причину, по которой это не должно быть общепринятым ответом? Он менее эффективен или менее эффективен?
Он рассматривает последовательные заглавные буквы как отдельные слова (например, ANZAC - это 5 слов), тогда как ответ MizardX рассматривает его (правильно ИМХО) как одно слово.
@Ray, я бы сказал, что «ANZAC» следует писать как «Anzac», чтобы его можно было считать словом паскальского падежа, поскольку это не английский регистр.
@Sam ANZAC - это аббревиатура от Австралии (и) Новозеландского армейского корпуса, поэтому его следует использовать заглавными буквами.
@Neaox, по-английски это должно быть, но это не аббревиатура или нормальный английский регистр; он разделен заглавными буквами. Если исходный текст должен быть написан с заглавной буквы так же, как и в обычном английском, то другие буквы также не должны быть заглавными. Например, почему «i» в «is» должно быть написано с заглавной буквы, чтобы соответствовать формату, разделенному заглавными буквами, а не «NZAC» в «ANZAC»? Строго говоря, если вы интерпретируете «АНЗАК» как разделенное заглавными буквами, то это 5 слов, по одному на каждую букву.
Это по-прежнему не работает на Take5 и преобразует "Arg20" в "arg 20" - терпит неудачу 3 из 4 моих тестов.
@Neaox официальные правила использования заглавных букв для C# говорит, что даже аббревиатуры (и другие инициалы) должны быть в нижнем регистре после первой буквы. Это пример HtmlTag, хотя HTML будет в верхнем регистре в любой ситуации, в которой будет ANZAC. Однако он делает исключение для двухуровневых сокращений, приводя в качестве примера IOStream (для IO Stream).
Решение наивного регулярного выражения. Не обрабатывает О'Коннера, а также добавляет пробел в начале строки.
s = "ThisIsMyCapsDelimitedString"
split = Regex.Replace(s, "[A-Z0-9]", " $&");
Я модифицировал вас, но люди обычно лучше переносят удар, если он не начинается с «наивного».
Не думаю, что это был удар. В этом контексте наивный обычно означает очевидное или простое (то есть не обязательно лучшее решение). Нет намерения оскорбить.
Просто для небольшого разнообразия ... Вот метод расширения, который не использует регулярное выражение.
public static class CamelSpaceExtensions
{
public static string SpaceCamelCase(this String input)
{
return new string(Enumerable.Concat(
input.Take(1), // No space before initial cap
InsertSpacesBeforeCaps(input.Skip(1))
).ToArray());
}
private static IEnumerable<char> InsertSpacesBeforeCaps(IEnumerable<char> input)
{
foreach (char c in input)
{
if (char.IsUpper(c))
{
yield return ' ';
}
yield return c;
}
}
}
Чтобы избежать использования Trim (), перед foreach я поставил: int counter = -1. внутри добавьте счетчик ++. измените проверку на: if (char.IsUpper (c) && counter> 0)
Это вставляет пробел перед первым символом.
Я взял на себя смелость исправить проблему, указанную @ZarShardan. Если вам не нравится это изменение, вы можете откатиться назад или изменить свое исправление.
Можно ли это улучшить для обработки сокращений, например, добавив пробел перед последним заглавным регистром в серии заглавных букв, например, BOEForecast => Прогноз Банка Англии
Я сделал это некоторое время назад. Он соответствует каждому компоненту имени CamelCase.
/([A-Z]+(?=$|[A-Z][a-z])|[A-Z]?[a-z]+)/g
Например:
"SimpleHTTPServer" => ["Simple", "HTTP", "Server"]
"camelCase" => ["camel", "Case"]
Чтобы преобразовать это, просто вставьте пробелы между словами:
Regex.Replace(s, "([a-z](?=[A-Z])|[A-Z](?=[A-Z][a-z]))", " ")
Если вам нужно обрабатывать цифры:
/([A-Z]+(?=$|[A-Z][a-z]|[0-9])|[A-Z]?[a-z]+|[0-9]+)/g
Regex.Replace(s,"([a-z](?=[A-Z]|[0-9])|[A-Z](?=[A-Z][a-z]|[0-9])|[0-9](?=[^0-9]))"," ")
CamelCase! Так это называлось! Я люблю это! Спасибо большое!
На самом деле camelCase имеет начальную строчную букву. Вы имеете в виду PascalCase.
... и когда вы говорите о чем-то, что может быть «верблюжьим футляром» или «паскальским футляром», это называется «перемежающийся».
Не разделяет Take5, что не подходит для моего варианта использования
@PandaWood Цифры не были в вопросе, поэтому в моем ответе они не учитывались. Я добавил вариант узора, который учитывает цифры.
я должен исправить неверную информацию. CamelCase необязательно начинать с строчной буквы. Начать можно с любого. По определению, PascalCase - это буквально Upper CamelCase.
Для большего разнообразия при использовании простых старых объектов C# следующий результат дает тот же результат, что и отличное регулярное выражение @ MizardX.
public string FromCamelCase(string camel)
{ // omitted checking camel for null
StringBuilder sb = new StringBuilder();
int upperCaseRun = 0;
foreach (char c in camel)
{ // append a space only if we're not at the start
// and we're not already in an all caps string.
if (char.IsUpper(c))
{
if (upperCaseRun == 0 && sb.Length != 0)
{
sb.Append(' ');
}
upperCaseRun++;
}
else if ( char.IsLower(c) )
{
if (upperCaseRun > 1) //The first new word will also be capitalized.
{
sb.Insert(sb.Length - 1, ' ');
}
upperCaseRun = 0;
}
else
{
upperCaseRun = 0;
}
sb.Append(c);
}
return sb.ToString();
}
Вау, это некрасиво. Теперь я вспоминаю, почему я так сильно люблю регулярные выражения! +1 за усилия, хотя. ;)
Отличный ответ, MizardX! Я немного изменил его, чтобы обрабатывать цифры как отдельные слова, чтобы «AddressLine1» превратился в «Address Line 1» вместо «Address Line1»:
Regex.Replace(s, "([a-z](?=[A-Z0-9])|[A-Z](?=[A-Z][a-z]))", " ")
Отличное дополнение! Я подозреваю, что немало людей будут удивлены принятым ответом на обработку чисел в строках. :)
Я знаю, что с тех пор, как вы опубликовали это, прошло почти 8 лет, но и у меня это сработало. :) Цифры меня сначала сбили с толку.
Единственный ответ, который проходит мои 2 теста на выбросы: «Take5» -> «Take 5», «PublisherID» -> «Publisher ID». Я хочу проголосовать за это дважды
Попробуй использовать
"([A-Z]*[^A-Z]*)"
Результат подойдет для смеси алфавита с цифрами.
Regex.Replace("AbcDefGH123Weh", "([A-Z]*[^A-Z]*)", " ");
Abc Def GH123 Weh
Regex.Replace("camelCase", "([A-Z]*[^A-Z]*)", " ");
camel Case
Мне нужно было решение, поддерживающее сокращения и числа. Это решение на основе Regex обрабатывает следующие шаблоны как отдельные «слова»:
Вы мог делаете это как однострочный:
Regex.Replace(value, @"(?<!^)((?<!\d)\d|(?(?<=[A-Z])[A-Z](?=[a-z])|[A-Z]))", " ")
Может быть лучше более читаемый подход:
using System.Text.RegularExpressions;
namespace Demo
{
public class IntercappedStringHelper
{
private static readonly Regex SeparatorRegex;
static IntercappedStringHelper()
{
const string pattern = @"
(?<!^) # Not start
(
# Digit, not preceded by another digit
(?<!\d)\d
|
# Upper-case letter, followed by lower-case letter if
# preceded by another upper-case letter, e.g. 'G' in HTMLGuide
(?(?<=[A-Z])[A-Z](?=[a-z])|[A-Z])
)";
var options = RegexOptions.IgnorePatternWhitespace | RegexOptions.Compiled;
SeparatorRegex = new Regex(pattern, options);
}
public static string SeparateWords(string value, string separator = " ")
{
return SeparatorRegex.Replace(value, separator + "");
}
}
}
Вот выдержка из тестов (XUnit):
[Theory]
[InlineData("PurchaseOrders", "Purchase-Orders")]
[InlineData("purchaseOrders", "purchase-Orders")]
[InlineData("2Unlimited", "2-Unlimited")]
[InlineData("The2Unlimited", "The-2-Unlimited")]
[InlineData("Unlimited2", "Unlimited-2")]
[InlineData("222Unlimited", "222-Unlimited")]
[InlineData("The222Unlimited", "The-222-Unlimited")]
[InlineData("Unlimited222", "Unlimited-222")]
[InlineData("ATeam", "A-Team")]
[InlineData("TheATeam", "The-A-Team")]
[InlineData("TeamA", "Team-A")]
[InlineData("HTMLGuide", "HTML-Guide")]
[InlineData("TheHTMLGuide", "The-HTML-Guide")]
[InlineData("TheGuideToHTML", "The-Guide-To-HTML")]
[InlineData("HTMLGuide5", "HTML-Guide-5")]
[InlineData("TheHTML5Guide", "The-HTML-5-Guide")]
[InlineData("TheGuideToHTML5", "The-Guide-To-HTML-5")]
[InlineData("TheUKAllStars", "The-UK-All-Stars")]
[InlineData("AllStarsUK", "All-Stars-UK")]
[InlineData("UKAllStars", "UK-All-Stars")]
+1 за объяснение регулярного выражения и его удобочитаемость. И я узнал кое-что новое. В .NET Regex есть режим свободного интервала и комментариев. Спасибо!
Ниже представлен прототип, который преобразует следующее в регистр заголовка:
Очевидно, вам понадобится только метод "ToTitleCase".
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text.RegularExpressions;
public class Program
{
public static void Main()
{
var examples = new List<string> {
"THEQuickBrownFox",
"theQUICKBrownFox",
"TheQuickBrownFOX",
"TheQuickBrownFox",
"the_quick_brown_fox",
"theFOX",
"FOX",
"QUICK"
};
foreach (var example in examples)
{
Console.WriteLine(ToTitleCase(example));
}
}
private static string ToTitleCase(string example)
{
var fromSnakeCase = example.Replace("_", " ");
var lowerToUpper = Regex.Replace(fromSnakeCase, @"(\p{Ll})(\p{Lu})", " ");
var sentenceCase = Regex.Replace(lowerToUpper, @"(\p{Lu}+)(\p{Lu}\p{Ll})", " ");
return new CultureInfo("en-US", false).TextInfo.ToTitleCase(sentenceCase);
}
}
Консоль будет выглядеть следующим образом:
THE Quick Brown Fox The QUICK Brown Fox The Quick Brown FOX The Quick Brown Fox The Quick Brown Fox The FOX FOX QUICK
Реализация псевдокода из: https://stackoverflow.com/a/5796394/4279201
private static StringBuilder camelCaseToRegular(string i_String)
{
StringBuilder output = new StringBuilder();
int i = 0;
foreach (char character in i_String)
{
if (character <= 'Z' && character >= 'A' && i > 0)
{
output.Append(" ");
}
output.Append(character);
i++;
}
return output;
}
Для соответствия между заглавными буквами и Категория Unicode в верхнем регистре: (?<=\P{Lu})(?=\p{Lu})
Dim s = Regex.Replace("CorrectHorseBatteryStaple", "(?<=\P{Lu})(?=\p{Lu})", " ")
Regex примерно в 10-12 раз медленнее простого цикла:
public static string CamelCaseToSpaceSeparated(this string str)
{
if (string.IsNullOrEmpty(str))
{
return str;
}
var res = new StringBuilder();
res.Append(str[0]);
for (var i = 1; i < str.Length; i++)
{
if (char.IsUpper(str[i]))
{
res.Append(' ');
}
res.Append(str[i]);
}
return res.ToString();
}
Процедурное и быстрое выполнение:
/// <summary>
/// Get the words in a code <paramref name = "identifier"/>.
/// </summary>
/// <param name = "identifier">The code <paramref name = "identifier"/></param> to extract words from.
public static string[] GetWords(this string identifier) {
Contract.Ensures(Contract.Result<string[]>() != null, "returned array of string is not null but can be empty");
if (identifier == null) { return new string[0]; }
if (identifier.Length == 0) { return new string[0]; }
const int MIN_WORD_LENGTH = 2; // Ignore one letter or one digit words
var length = identifier.Length;
var list = new List<string>(1 + length/2); // Set capacity, not possible more words since we discard one char words
var sb = new StringBuilder();
CharKind cKindCurrent = GetCharKind(identifier[0]); // length is not zero here
CharKind cKindNext = length == 1 ? CharKind.End : GetCharKind(identifier[1]);
for (var i = 0; i < length; i++) {
var c = identifier[i];
CharKind cKindNextNext = (i >= length - 2) ? CharKind.End : GetCharKind(identifier[i + 2]);
// Process cKindCurrent
switch (cKindCurrent) {
case CharKind.Digit:
case CharKind.LowerCaseLetter:
sb.Append(c); // Append digit or lowerCaseLetter to sb
if (cKindNext == CharKind.UpperCaseLetter) {
goto TURN_SB_INTO_WORD; // Finish word if next char is upper
}
goto CHAR_PROCESSED;
case CharKind.Other:
goto TURN_SB_INTO_WORD;
default: // charCurrent is never Start or End
Debug.Assert(cKindCurrent == CharKind.UpperCaseLetter);
break;
}
// Here cKindCurrent is UpperCaseLetter
// Append UpperCaseLetter to sb anyway
sb.Append(c);
switch (cKindNext) {
default:
goto CHAR_PROCESSED;
case CharKind.UpperCaseLetter:
// "SimpleHTTPServer" when we are at 'P' we need to see that NextNext is 'e' to get the word!
if (cKindNextNext == CharKind.LowerCaseLetter) {
goto TURN_SB_INTO_WORD;
}
goto CHAR_PROCESSED;
case CharKind.End:
case CharKind.Other:
break; // goto TURN_SB_INTO_WORD;
}
//------------------------------------------------
TURN_SB_INTO_WORD:
string word = sb.ToString();
sb.Length = 0;
if (word.Length >= MIN_WORD_LENGTH) {
list.Add(word);
}
CHAR_PROCESSED:
// Shift left for next iteration!
cKindCurrent = cKindNext;
cKindNext = cKindNextNext;
}
string lastWord = sb.ToString();
if (lastWord.Length >= MIN_WORD_LENGTH) {
list.Add(lastWord);
}
return list.ToArray();
}
private static CharKind GetCharKind(char c) {
if (char.IsDigit(c)) { return CharKind.Digit; }
if (char.IsLetter(c)) {
if (char.IsUpper(c)) { return CharKind.UpperCaseLetter; }
Debug.Assert(char.IsLower(c));
return CharKind.LowerCaseLetter;
}
return CharKind.Other;
}
enum CharKind {
End, // For end of string
Digit,
UpperCaseLetter,
LowerCaseLetter,
Other
}
Тесты:
[TestCase((string)null, "")]
[TestCase("", "")]
// Ignore one letter or one digit words
[TestCase("A", "")]
[TestCase("4", "")]
[TestCase("_", "")]
[TestCase("Word_m_Field", "Word Field")]
[TestCase("Word_4_Field", "Word Field")]
[TestCase("a4", "a4")]
[TestCase("ABC", "ABC")]
[TestCase("abc", "abc")]
[TestCase("AbCd", "Ab Cd")]
[TestCase("AbcCde", "Abc Cde")]
[TestCase("ABCCde", "ABC Cde")]
[TestCase("Abc42Cde", "Abc42 Cde")]
[TestCase("Abc42cde", "Abc42cde")]
[TestCase("ABC42Cde", "ABC42 Cde")]
[TestCase("42ABC", "42 ABC")]
[TestCase("42abc", "42abc")]
[TestCase("abc_cde", "abc cde")]
[TestCase("Abc_Cde", "Abc Cde")]
[TestCase("_Abc__Cde_", "Abc Cde")]
[TestCase("ABC_CDE_FGH", "ABC CDE FGH")]
[TestCase("ABC CDE FGH", "ABC CDE FGH")] // Should not happend (white char) anything that is not a letter/digit/'_' is considered as a separator
[TestCase("ABC,CDE;FGH", "ABC CDE FGH")] // Should not happend (,;) anything that is not a letter/digit/'_' is considered as a separator
[TestCase("abc<cde", "abc cde")]
[TestCase("abc<>cde", "abc cde")]
[TestCase("abc<D>cde", "abc cde")] // Ignore one letter or one digit words
[TestCase("abc<Da>cde", "abc Da cde")]
[TestCase("abc<cde>", "abc cde")]
[TestCase("SimpleHTTPServer", "Simple HTTP Server")]
[TestCase("SimpleHTTPS2erver", "Simple HTTPS2erver")]
[TestCase("camelCase", "camel Case")]
[TestCase("m_Field", "Field")]
[TestCase("mm_Field", "mm Field")]
public void Test_GetWords(string identifier, string expectedWordsStr) {
var expectedWords = expectedWordsStr.Split(' ');
if (identifier == null || identifier.Length <= 1) {
expectedWords = new string[0];
}
var words = identifier.GetWords();
Assert.IsTrue(words.SequenceEqual(expectedWords));
}
Простое решение, которое должно быть на порядок быстрее, чем решение с регулярным выражением (на основе тестов, которые я провел с лучшими решениями в этом потоке), особенно при увеличении размера входной строки:
string s1 = "ThisIsATestStringAbcDefGhiJklMnoPqrStuVwxYz";
string s2;
StringBuilder sb = new StringBuilder();
foreach (char c in s1)
sb.Append(char.IsUpper(c)
? " " + c.ToString()
: c.ToString());
s2 = sb.ToString();
Что происходит, когда вам приходится иметь дело с «OldMacDonaldAndMrO'TooleWentToMcDonalds»?