Подтвердить что ты не робот

Regex. Корпус верблюда для подчеркивания. Игнорировать первое появление

Например:

thisIsMySample 

должен быть:

this_Is_My_Sample

Мой код:

System.Text.RegularExpressions.Regex.Replace(input, "([A-Z])", "_$0", System.Text.RegularExpressions.RegexOptions.Compiled);

Он отлично работает, но если вход изменяется на:

ThisIsMySample

вывод будет:

_This_Is_My_Sample

Как игнорировать первое появление?

4b9b3361

Ответ 1

Нерегрессивное решение

string result = string.Concat(input.Select((x,i) => i > 0 && char.IsUpper(x) ? "_" + x.ToString() : x.ToString())); 

Кажется, тоже довольно быстро: Regex: 2569ms, С#: 1489ms

Stopwatch stp = new Stopwatch();
stp.Start();
for (int i = 0; i < 1000000; i++)
{
    string input = "ThisIsMySample";
    string result = System.Text.RegularExpressions.Regex.Replace(input, "(?<=.)([A-Z])", "_$0",
            System.Text.RegularExpressions.RegexOptions.Compiled);
}
stp.Stop();
MessageBox.Show(stp.ElapsedMilliseconds.ToString());
// Result 2569ms

Stopwatch stp2 = new Stopwatch();
stp2.Start();
for (int i = 0; i < 1000000; i++)
{
    string input = "ThisIsMySample";
    string result = string.Concat(input.Select((x, j) => j > 0 && char.IsUpper(x) ? "_" + x.ToString() : x.ToString()));
}
stp2.Stop();
MessageBox.Show(stp2.ElapsedMilliseconds.ToString());
// Result: 1489ms

Ответ 2

Вы можете использовать lookbehind, чтобы каждому соответствию предшествовал хотя бы один символ:

System.Text.RegularExpressions.Regex.Replace(input, "(?<=.)([A-Z])", "_$0",
                      System.Text.RegularExpressions.RegexOptions.Compiled);

lookaheads и lookbehind позволяют вам делать утверждения о тексте, окружающем совпадение, без включения этого текста в соответствие.

Ответ 3

Возможно, нравится:

var str = Regex.Replace(input, "([A-Z])", "_$0", RegexOptions.Compiled);
if(str.StartsWith("_"))
   str = str.SubString(1);

Ответ 4

Разрабатывая решение sa_ddam213, моя расширяет это:

public static string GetConstStyleName(this string value)
        {
            return string.Concat(value.Select((x, i) =>
            {
                //want to avoid putting underscores between pairs of upper-cases or pairs of numbers, or adding redundant underscores if they already exist.
                bool isPrevCharLower = (i == 0) ? false : char.IsLower(value[i - 1]);
                bool isPrevCharNumber = (i == 0) ? false : char.IsNumber(value[i - 1]);
                return (isPrevCharLower && (char.IsUpper(x) || char.IsNumber(x))) //lower-case followed by upper-case or number needs underscore
                    || (isPrevCharNumber && (char.IsUpper(x))) //number followed by upper-case needs underscore
                    ? "_" + x.ToString() : x.ToString();
            })).ToUpperInvariant();
        }

Ответ 5

// (Preceded by a lowercase character or digit) (a capital) => The character prefixed with an underscore
var result = Regex.Replace(input, "(?<=[a-z0-9])[A-Z]", m => "_" + m.Value);
result = result.ToLowerInvariant();
  • Это работает как для PascalCase, так и camelCase.
  • Он не создает никаких подчеркиваний подчеркивания.
  • Он оставляет в тактике любые последовательности символов, отличных от слова, и подчеркивания в строке, потому что они кажутся преднамеренными, например. __HiThere_Guys становится __hi_there_guys.
  • Суффиксы цифр (намеренно) считаются частью слова, например. NewVersion3 становится new_version3.
  • Префикс для цифр следует за исходным корпусом, например. 3VersionsHere становится 3_versions_here, но 3rdVersion становится 3rd_version.
  • К сожалению, заглавные двухбуквенные аббревиатуры (например, в IDNumber, где ID будут считаться отдельным словом), как предложено в Соглашения Microsoft Capitalization, не поддерживаются, поскольку они противоречат другим случаям. Я рекомендую, в общем, противостоять этому руководству, поскольку это, по-видимому, произвольное исключение из конвенции о том, чтобы не использовать аббревиатуры. Придерживайтесь IDNumber.

Ответ 6

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

.([A-Z])

Вышеупомянутое регулярное выражение просто исключает каждый char, который приходит первым, и поскольку он не находится в фигурных скобках, он будет в группе соответствия.

Теперь вам нужно сопоставить вторую группу, как отметил Бибху:

System.Text.RegularExpressions.Regex.Replace(s, "(.)([A-Z])", "$1_$2", System.Text.RegularExpressions.RegexOptions.Compiled);

Ответ 7

Используйте ".([A-Z])" для вашего регулярного выражения, а затем "_$1" для замены. Таким образом, вы используете захваченную строку для замены и с ведущим . вы уверены, что не поймаете первую char вашей строки.

Ответ 8

Вы можете добиться лучшей производительности, используя следующую реализацию вместо Regex и Linq, также он использует тип Span для меньшего выделения:

public ReadOnlySpan<char> ToSnakeCaseBySpan(string name)
{
    int upperCaseLength = 0;
    for (int i = 0; i < name.Length; i++)
    {
        if (name[i] >= 'A' && name[i] <= 'Z' && name[i] != name[0])
        {
            upperCaseLength++;
        }
    }
    int bufferSize = name.Length + upperCaseLength;
    Span<char> buffer = new char[bufferSize];
    int bufferPosition = 0;
    int namePosition = 0;
    while (bufferPosition < buffer.Length)
    {
        if (namePosition > 0 && name[namePosition] >= 'A' && name[namePosition] <= 'Z')
        {
            buffer[bufferPosition] = '_';
            buffer[bufferPosition + 1] = name[namePosition];
            bufferPosition += 2;
            namePosition++;
            continue;
        }
        buffer[bufferPosition] = name[namePosition];
        bufferPosition++;
        namePosition++;
    }
    return buffer;
}

Бенчмарки

|                          Method |        Mean |      Error |     StdDev | Rank |  Gen 0 | Gen 1 | Gen 2 | Allocated |
|-------------------------------- |------------:|-----------:|-----------:|-----:|-------:|------:|------:|----------:|
|               ToSnakeCaseBySpan |    27.47 ns |  0.4629 ns |  0.4330 ns |    1 | 0.0153 |     - |     - |      48 B |
|  ToSnakeCaseStringBuilderBySpan |    85.23 ns |  1.6495 ns |  1.3774 ns |    2 | 0.0637 |     - |     - |     200 B |
|       ToSnakeCaseNewtonsoftJson |    85.72 ns |  1.6418 ns |  1.4554 ns |    2 | 0.0484 |     - |     - |     152 B |
| ToSnakeCaseNewtonsoftJsonBySpan |    86.96 ns |  1.7060 ns |  1.5958 ns |    2 | 0.0484 |     - |     - |     152 B |
|                 ToSnakeCaseLinq |   353.42 ns |  3.9670 ns |  3.7108 ns |    3 | 0.1450 |     - |     - |     456 B |
|                ToSnakeCaseRegex | 2,056.69 ns | 29.5694 ns | 26.2125 ns |    4 | 0.1526 |     - |     - |     496 B |

Readmore