2010-07-09 2 views
6

Я ищу способ разделить строки PascalCase, например. «MyString», в отдельные слова - «My», «String». Другой пользователь posed the question for bash, но я хочу знать, как это сделать с помощью обычных регулярных выражений или, по крайней мере, в .NET.Разделите строку PascalCase на отдельные слова

Бонус, если вы можете найти способ также разделить (и, возможно, заглавные) строки camelCase: например. «myString» становится «my» и «String», с возможностью делать заглавные буквы или строчные строки либо или обе строки.

+0

Возможный дубликат [есть элегантный способ разобрать слово и добавить пробелы перед прописными буквами] (http://stackoverflow.com/questions/3103730/is-there-a-elegant-way-to-parse- a-word-and-add-spaces-before-capital-letters) –

+0

Этот вопрос относится только к .NET, но ответы в регулярном выражении могут быть применены в другом месте. – Pat

+0

Проверьте вопрос об ошибке: принятый ответ имеет регулярное выражение для разделения 'AnXMLAndXSLT2.0Tool' на' [An] [XML] [And] [XSLT] [2.0] [Tool] '. Он использует взгляды, которые можно утверждать, вполне читабельны. – polygenelubricants

ответ

13

См. Этот вопрос: Is there a elegant way to parse a word and add spaces before capital letters? Его принятый ответ охватывает то, что вы хотите, включая цифры и несколько прописных букв подряд. Хотя этот образец имеет слова, начинающиеся в верхнем регистре, это равнозначно, когда первое слово находится в нижнем регистре.

string[] tests = { 
    "AutomaticTrackingSystem", 
    "XMLEditor", 
    "AnXMLAndXSLT2.0Tool", 
}; 


Regex r = new Regex(
    @"(?<=[A-Z])(?=[A-Z][a-z])|(?<=[^A-Z])(?=[A-Z])|(?<=[A-Za-z])(?=[^A-Za-z])" 
); 

foreach (string s in tests) 
    r.Replace(s, " "); 

выше выход будет:

[Automatic][Tracking][System] 
[XML][Editor] 
[An][XML][And][XSLT][2.0][Tool] 
+0

. Принятый ответ - это еще одно решение на основе RegExp. –

+2

@Steven Sudit: Да. RegEx - один из лучших инструментов для этот вопрос возникает, а другой вопрос просто вымывается с большим набором примеров использования. – chilltemp

+0

@chilltemp, знаете ли вы о встроенной функции для этого? – Shimmy

1
var regex = new Regex("([A-Z]+[^A-Z]+)"); 
var matches = regex.Matches("aCamelCaseWord") 
    .Cast<Match>() 
    .Select(match => match.Value); 
foreach (var element in matches) 
{ 
    Console.WriteLine(element); 
} 

Печать

Camel 
Case 
Word 

(Как вы можете видеть, он не обрабатывает верблюжьего. - он упал ведущий "а")

+0

1) Скомпилируйте регулярное выражение для некоторой скорости. 2) Это все равно будет медленнее, чем делать это вручную. –

+0

@Steven Я согласен, что он должен быть скомпилирован для скорости, но это функциональность, на которой я собираюсь сейчас. Что значит «медленнее, чем делать это вручную»? Если я размышляю над объектом с кучей публичных свойств и преобразовываю имена из PascalCase в отдельные слова, это будет намного быстрее (время разработки и обслуживания), делая это программно, чем вручную. – Pat

+0

Я не видел скорость, указанную в качестве требования. Также я думаю, что «делать это вручную» означает писать собственный код разбора строк, который * может * быть быстрее, но * будет * значительно больше кода и больше тестирования. –

0

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

Что-то вроде: \W([A-Z][A-Za-z]+)+

Для: sdcsds sd aCamelCaseWord as dasd as aSscdcacdcdc PascelCase DfsadSsdd sd Выходы:

48: PascelCase 
59: DfsadSsdd 
+0

Хммм.Это не работает прямо для регулярного выражения .NET, но, возможно, с небольшой документацией, копающей ... – Pat

+0

Обновлено с действующим рабочим регулярным выражением. –

+0

Вы должны использовать '\ b' (границу слова) в соответствии с началом слова, а не' \ W'. –

0

В Ruby:

"aCamelCaseWord".split /(?=[[:upper:]])/ 
=> ["a", "Camel", "Case", "Word"] 

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

+0

Это позитивный взгляд, не так ли? Я не могу получить эквивалент для работы в .NET, даже когда я заменяю '[[: upper:]]' на '[A-Z]' (http://en.wikipedia.org/wiki/Regular_expression). – Pat

+0

.NET regex не поддерживает синтаксис класса символов POSIX. Вместо этого вы можете использовать '\ p {Lu}', но '[A-Z]', вероятно, будет достаточно. Во всяком случае, этот подход слишком упрощен. Проверьте другой вопрос, в частности, появилось сообщение 'split' regex @poly. Это действительно сложно. –

+0

@Pat: эта статья в Википедии не предназначена для использования в качестве ссылки; слишком общий и слишком теоретический. Этот сайт намного полезнее: http://www.regular-expressions.info/ –

5

Answered in a different question:

void Main() 
{ 
    "aCamelCaseWord".ToFriendlyCase().Dump(); 
} 

public static class Extensions 
{ 
    public static string ToFriendlyCase(this string PascalString) 
    { 
     return Regex.Replace(PascalString, "(?!^)([A-Z])", " $1"); 
    } 
} 

a Camel Case Word Выходы (.Dump() просто печатает на консоль).

+0

Что должно произойти для таких строк: 'aCamelCaseXML'? Читая вопрос, я бы ожидал «XML Camel Case». Вместо этого он дает «случай верблюда X M L». –

+0

@MainMa Это правда. Следуя стандартам именования .NET, любые аббревиатуры, содержащие три буквы или более длинные (например, XML), будут в надлежащем случае (например, Xml), но двухбуквенные аббревиатуры (например, IP для IPAddress) все равно вызовут проблему. Было бы лучше, если бы алгоритм обрабатывал этот случай. – Pat

+0

Есть ли какие-нибудь функциональные функции, которые делают это? – Shimmy

3

Как насчет:

static IEnumerable<string> SplitPascalCase(this string text) 
{ 
    var sb = new StringBuilder(); 
    using (var reader = new StringReader(text)) 
    { 
     while (reader.Peek() != -1) 
     { 
      char c = (char)reader.Read(); 
      if (char.IsUpper(c) && sb.Length > 0) 
      { 
       yield return sb.ToString(); 
       sb.Length = 0; 
      } 

      sb.Append(c); 
     } 
    } 

    if (sb.Length > 0) 
     yield return sb.ToString(); 
} 
+0

Это было бы «вручную». –

+0

@Steven Sudit: Да ... это было запрещено или что-то в этом роде? –

+0

Нет, нет, совсем нет. Было немного замешательство в том, что означало «вручную», когда я предложил Пату как альтернативу RegExp. На самом деле, я думаю, что RegExp, несмотря на всю свою власть, злоупотребляет. Для многих рабочих мест это плохо подходит, что приводит к загадочному коду и низкой производительности. –

7

Просто, чтобы обеспечить альтернативу RegEx и циклических решений все готовые при условии, здесь ответ, используя LINQ, который также обрабатывает футляр для верблюдов и акронимы:

string[] testCollection = new string[] { "AutomaticTrackingSystem", "XSLT", "aCamelCaseWord" }; 
    foreach (string test in testCollection) 
    { 
     // if it is not the first character and it is uppercase 
     // and the previous character is not uppercase then insert a space 
     var result = test.SelectMany((c, i) => i != 0 && char.IsUpper(c) && !char.IsUpper(test[i - 1]) ? new char[] { ' ', c } : new char[] { c }); 
     Console.WriteLine(new String(result.ToArray())); 
    } 

Выхода из этого:

Automatic Tracking System 
XSLT 
a Camel Case Word 
+0

Это мой абсолютный фаворит :) – kzu

2

с целями

  • а) Создание функции, которая оптимизирует производительность
  • б) имеют свой собственный взгляд на CamelCase, в котором капитализированную аббревиатуре не была (я полностью согласен, что это не стандартное определение случая с верблюдом или паскалем, но это не необычное использование): «TestTLAContainingCamelCase» становится «Test TLA Containing Camel Case» (TLA = Three Letter)

поэтому я создал следующий (не регулярное выражение, многословный, но производительность ориентированной) функции

public static string ToSeparateWords(this string value) 
{ 
    if (value==null){return null;} 
    if(value.Length <=1){return value;} 
    char[] inChars = value.ToCharArray(); 
    List<int> uCWithAnyLC = new List<int>(); 
    int i = 0; 
    while (i < inChars.Length && char.IsUpper(inChars[i])) { ++i; } 
    for (; i < inChars.Length; i++) 
    { 
     if (char.IsUpper(inChars[i])) 
     { 
      uCWithAnyLC.Add(i); 
      if (++i < inChars.Length && char.IsUpper(inChars[i])) 
      { 
       while (++i < inChars.Length) 
       { 
        if (!char.IsUpper(inChars[i])) 
        { 
         uCWithAnyLC.Add(i - 1); 
         break; 
        } 
       } 
      } 
     } 
    } 
    char[] outChars = new char[inChars.Length + uCWithAnyLC.Count]; 
    int lastIndex = 0; 
    for (i=0;i<uCWithAnyLC.Count;i++) 
    { 
     int currentIndex = uCWithAnyLC[i]; 
     Array.Copy(inChars, lastIndex, outChars, lastIndex + i, currentIndex - lastIndex); 
     outChars[currentIndex + i] = ' '; 
     lastIndex = currentIndex; 
    } 
    int lastPos = lastIndex + uCWithAnyLC.Count; 
    Array.Copy(inChars, lastIndex, outChars, lastPos, outChars.Length - lastPos); 
    return new string(outChars); 
} 

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

regex pattern used = "([a-z](?=[A-Z])|[A-Z](?=[A-Z][a-z]))" 
test string = "TestTLAContainingCamelCase": 
static regex:  13 302ms 
Regex instance: 12 398ms 
compiled regex: 12 663ms 
brent(above):   345ms 
AndyRose:   1 764ms 
DanTao:    995ms 

метод экземпляра Regex был лишь немного быстрее, чем статический метод, даже более миллиона итераций (и я не могу видеть преимущество использования RegexOptions.Compiled флага), и очень сжатый код Дэн Тао был почти таким же быстрым, как мой гораздо менее четкий код!

+0

Отличный бенчмаркинг! – trailmax

0
public static string PascalCaseToSentence(string input) 
    { 
     if (input == null) return ""; 

     string output = Regex.Replace(input, @"(?<=[A-Z])(?=[A-Z][a-z])|(?<=[^A-Z])(?=[A-Z])|(?<=[A-Za-z])(?=[^A-Za-z])", m => " " + m.Value); 
     return output; 
    } 

Основано на ответе Шимми.

Смежные вопросы