.NET - Как можно разбить строку с разделителями "заглавными буквами" на массив?

Как перейти от этой строки: "ThisIsMyCapsDelimitedString"

... к этой строке: "This Is My Caps Delimited String"

Желательно минимальное количество строк кода на VB.net, но приветствуется и C#.

Ваше здоровье!

Что происходит, когда вам приходится иметь дело с «OldMacDonaldAndMrO'TooleWentToMcDonalds»?

Grant Wagner 01.10.2008 02:09

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

Matias Nino 01.10.2008 02:18

Это сработало для меня: Regex.Replace(s, "([A-Z0-9]+)", " ").Trim(). А если вы хотите разделить каждую заглавную букву, просто уберите плюс.

Mladen B. 25.03.2019 23:17
Стоит ли изучать 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 называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
114
3
21 513
17
Перейти к ответу Данный вопрос помечен как решенный

Ответы 17

Вероятно, есть более элегантное решение, но вот что я придумал сразу:

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 () и т. д. По вашему выбору. :)

Pseudo Masochist 04.10.2008 02:40

string s = "ThisIsMyCapsDelimitedString";
string t = Regex.Replace(s, "([A-Z])", " ").Substring(1);

Я знал, что будет простой способ RegEx ... Мне нужно больше его использовать.

Max Schmeling 01.10.2008 02:17

Не гуру регулярных выражений, но что происходит с "HeresAWTFString"?

Nick 01.10.2008 02:24

Вы получаете «Heres A W T F String», но именно это Матиас Нино просил в своем вопросе.

Max Schmeling 01.10.2008 02:31

Да, ему нужно добавить, что «несколько соседних столиц не трогают». Что довольно очевидно во многих случаях, например, "PublisherID" здесь идет к "Publisher I D", что ужасно.

PandaWood 29.03.2017 03:59

Regex.Replace("ThisIsMyCapsDelimitedString", "(\B[A-Z])", " ")

На данный момент это лучшее решение, но вам нужно использовать \\ B для компиляции. В противном случае компилятор пытается рассматривать \ B как escape-последовательность.

Ferruccio 01.10.2008 04:07

Хорошее решение. Может ли кто-нибудь придумать причину, по которой это не должно быть общепринятым ответом? Он менее эффективен или менее эффективен?

Drew Noakes 25.08.2010 04:34

Он рассматривает последовательные заглавные буквы как отдельные слова (например, ANZAC - это 5 слов), тогда как ответ MizardX рассматривает его (правильно ИМХО) как одно слово.

Ray 28.12.2011 14:50

@Ray, я бы сказал, что «ANZAC» следует писать как «Anzac», чтобы его можно было считать словом паскальского падежа, поскольку это не английский регистр.

Sam 20.06.2014 08:16

@Sam ANZAC - это аббревиатура от Австралии (и) Новозеландского армейского корпуса, поэтому его следует использовать заглавными буквами.

Neaox 05.08.2014 08:20

@Neaox, по-английски это должно быть, но это не аббревиатура или нормальный английский регистр; он разделен заглавными буквами. Если исходный текст должен быть написан с заглавной буквы так же, как и в обычном английском, то другие буквы также не должны быть заглавными. Например, почему «i» в «is» должно быть написано с заглавной буквы, чтобы соответствовать формату, разделенному заглавными буквами, а не «NZAC» в «ANZAC»? Строго говоря, если вы интерпретируете «АНЗАК» как разделенное заглавными буквами, то это 5 слов, по одному на каждую букву.

Sam 05.08.2014 08:32

Это по-прежнему не работает на Take5 и преобразует "Arg20" в "arg 20" - терпит неудачу 3 из 4 моих тестов.

PandaWood 29.03.2017 04:03

@Neaox официальные правила использования заглавных букв для C# говорит, что даже аббревиатуры (и другие инициалы) должны быть в нижнем регистре после первой буквы. Это пример HtmlTag, хотя HTML будет в верхнем регистре в любой ситуации, в которой будет ANZAC. Однако он делает исключение для двухуровневых сокращений, приводя в качестве примера IOStream (для IO Stream).

Arthur Tacca 07.01.2019 14:49

Решение наивного регулярного выражения. Не обрабатывает О'Коннера, а также добавляет пробел в начале строки.

s = "ThisIsMyCapsDelimitedString"
split = Regex.Replace(s, "[A-Z0-9]", " $&");

Я модифицировал вас, но люди обычно лучше переносят удар, если он не начинается с «наивного».

MusiGenesis 01.10.2008 02:42

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

Ferruccio 01.10.2008 03:58

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

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)

Outside the Box Developer 30.06.2017 02:23

Это вставляет пробел перед первым символом.

Zar Shardan 04.10.2017 17:23

Я взял на себя смелость исправить проблему, указанную @ZarShardan. Если вам не нравится это изменение, вы можете откатиться назад или изменить свое исправление.

jpmc26 28.11.2018 23:59

Можно ли это улучшить для обработки сокращений, например, добавив пробел перед последним заглавным регистром в серии заглавных букв, например, BOEForecast => Прогноз Банка Англии

Nepaluz 19.07.2019 14:38
Ответ принят как подходящий

Я сделал это некоторое время назад. Он соответствует каждому компоненту имени 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! Так это называлось! Я люблю это! Спасибо большое!

Matias Nino 01.10.2008 03:27

На самом деле camelCase имеет начальную строчную букву. Вы имеете в виду PascalCase.

Drew Noakes 12.02.2009 17:05

... и когда вы говорите о чем-то, что может быть «верблюжьим футляром» или «паскальским футляром», это называется «перемежающийся».

Chris 06.05.2010 20:16

Не разделяет Take5, что не подходит для моего варианта использования

PandaWood 29.03.2017 03:50

@PandaWood Цифры не были в вопросе, поэтому в моем ответе они не учитывались. Я добавил вариант узора, который учитывает цифры.

Markus Jarderot 29.03.2017 14:50

я должен исправить неверную информацию. CamelCase необязательно начинать с строчной буквы. Начать можно с любого. По определению, PascalCase - это буквально Upper CamelCase.

John Lord 01.01.2021 03:35

Для большего разнообразия при использовании простых старых объектов 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 за усилия, хотя. ;)

Mark Brackett 01.10.2008 07:22

Отличный ответ, MizardX! Я немного изменил его, чтобы обрабатывать цифры как отдельные слова, чтобы «AddressLine1» превратился в «Address Line 1» вместо «Address Line1»:

Regex.Replace(s, "([a-z](?=[A-Z0-9])|[A-Z](?=[A-Z][a-z]))", " ")

Отличное дополнение! Я подозреваю, что немало людей будут удивлены принятым ответом на обработку чисел в строках. :)

Jordan Gray 12.11.2012 18:37

Я знаю, что с тех пор, как вы опубликовали это, прошло почти 8 лет, но и у меня это сработало. :) Цифры меня сначала сбили с толку.

Michael Armes 27.08.2016 00:05

Единственный ответ, который проходит мои 2 теста на выбросы: «Take5» -> «Take 5», «PublisherID» -> «Publisher ID». Я хочу проголосовать за это дважды

PandaWood 29.03.2017 04:08

Попробуй использовать

"([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 обрабатывает следующие шаблоны как отдельные «слова»:

  • Заглавная буква, за которой следуют строчные буквы
  • Последовательность последовательных чисел
  • Последовательные заглавные буквы (интерпретируются как акронимы) - новое слово может начинаться с последней заглавной буквы, например HTMLGuide => "Руководство по HTML", "TheATeam" => "Команда A"

Вы мог делаете это как однострочный:

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 есть режим свободного интервала и комментариев. Спасибо!

Felix Keil 28.08.2015 14:33

Ниже представлен прототип, который преобразует следующее в регистр заголовка:

  • snake_case
  • верблюд
  • PascalCase
  • приговор
  • Регистр заголовка (сохранить текущее форматирование)

Очевидно, вам понадобится только метод "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();

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