.NET - 如何将以“大写字母”为分隔符的字符串拆分成数组?

123
如何将这个字符串进行转换:"ThisIsMyCapsDelimitedString",转成 "This Is My Caps Delimited String"?
最好能用 VB.net 写出最简洁的代码,当然 C# 也可以。
祝好!

1
当你需要处理"OldMacDonaldAndMrO'TooleWentToMcDonalds"时会发生什么? - Grant Wagner
2
它只会被有限地使用。我主要只会用它来解析变量名,例如ThisIsMySpecialVariable。 - Matias Nino
这对我有用:Regex.Replace(s, "([A-Z0-9]+)", " $1").Trim()。如果你想在每个大写字母处拆分,只需删除加号。 - Mladen B.
19个回答

187

我之前做过这个。它可以匹配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]))", "$1 ")

如果您需要处理数字:

/([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]))","$1 ")

1
驼峰命名法!就是这个名字!我喜欢它!非常感谢! - Matias Nino
21
实际上camelCase有一个小写字母开头。你在这里提到的是PascalCase。 - Drew Noakes
12
...而当你提到可以是“驼峰式”或“帕斯卡式”的东西时,它被称为“交替大写”。 - Chris
1
@PandaWood 数字不在问题中,所以我的答案没有考虑到它们。我添加了一种考虑数字的模式变量。 - Markus Jarderot
1
我必须更正错误信息。CamelCase不一定要以小写字母开头,它可以以任意大小写字母开头。PascalCase定义上就是指首字母大写的Upper CamelCase。 - John Lord
显示剩余2条评论

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

8
这个翻译引擎将连续的大写字母视为单独的单词(例如,ANZAC被视为5个单词),而MizardX的答案则将其(在我看来正确地)视为一个单词。 - Ray
2
@Ray,我认为“ANZAC”应该写成“Anzac”,以便被视为pascal case单词,因为它不是英语大小写。 - Sam
1
@Neaox,这应该是英文,但它不是首字母缩写或正常的英文大小写;它是大写分隔符。如果源文本应该像正常的英语一样大写,那么其他字母也不应该大写。例如,为什么“is”中的“i”要大写以适应大写分隔符格式,而“ANZAC”中的“NZAC”却不大写呢?严格来说,如果您将“ANZAC”解释为大写分隔符,则它有5个单词,每个单词都有一个字母。 - Sam
这仍然在“Take5”上失败,并将“Arg20”转换为“arg 20” - 在我的4个测试中有3个失败。 - PandaWood
@Neaox C#的官方大写规范指出,即使是缩略词(和其他首字母缩写),在第一个字母之后也应该小写。例如,它给出了HtmlTag的例子,即使在每种情况下HTML都应该是大写的,而ANZAC则不同。然而,它对于两级缩写做出了例外,以IOStream作为示例(用于IO流)。 - Arthur Tacca
显示剩余3条评论

22

很棒的回答,MizardX!我稍微修改了一下,将数字视为单独的单词,这样"AddressLine1"就会变成"Address Line 1"而不是"Address Line1"。

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

2
太好了!我怀疑很多人会对接受的答案在处理字符串中的数字时感到惊讶。 :) - Jordan Gray
我知道你发布这篇文章已经快8年了,但是它对我来说也完美地解决了问题。 :) 起初数字让我有些困惑。 - Michael Armes
通过我的两个异常值测试,唯一通过的答案是:“Take5” -> “Take 5”,“PublisherID” -> “Publisher ID”。我想要给这个答案点赞两次。 - PandaWood

18

为了变换一下口味...这里提供一个不使用正则表达式的扩展方法。

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。在循环内部,添加counter++。将检查更改为:if (char.IsUpper(c) && counter > 0)。 - Outside the Box Developer
这将在第一个字符之前插入一个空格。 - Zar Shardan
我已经擅自修复了@ZarShardan指出的问题。如果您不喜欢这个更改,请随意回滚或编辑为您自己的修复。 - jpmc26
这个能否增强处理缩写词的能力,例如在一系列大写字母中的最后一个大写字母前添加一个空格,例如 BOEForecast => BOE Forecast - Nepaluz

12
Grant Wagner的优秀评论搁置不谈:
Dim s As String = RegularExpressions.Regex.Replace("ThisIsMyCapsDelimitedString", "([A-Z])", " $1")

好主意... 请随意插入您选择的.substring()、.trimstart()、.trim()、.remove()等函数。 :) - Pseudo Masochist

11

我需要一个支持缩写词和数字的解决方案。这个基于正则表达式的解决方案将以下模式视为单独的“单词”:

  • 一个大写字母后面跟着小写字母
  • 连续的数字序列
  • 连续的大写字母(被解释为缩略语)- 新单词可以使用最后一个大写字母开始,例如HTMLGuide =>“HTML指南”,“TheATeam”=>“The A团队”。

可以把它做成一行代码:

Regex.Replace(value, @"(?<!^)((?<!\d)\d|(?(?<=[A-Z])[A-Z](?=[a-z])|[A-Z]))", " $1")

一个更易读的方法可能更好:

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 + "$1");
        }
    }
}

以下是(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
+1 为解释正则表达式并使其易读而加分。我学到了新东西。在.NET Regex中有自由空间模式和注释。谢谢! - Felix Keil

4

为了更多样化,使用普通的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();
}

2
哇,那真的很丑。现在我记得为什么我如此热爱正则表达式了!不过还是要给你加一分的努力。 ;) - Mark Brackett

3
以下是一个原型,可以将以下内容转换为标题大小写:
  • snake_case
  • camelCase
  • 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})", "$1 $2");
        var sentenceCase = Regex.Replace(lowerToUpper, @"(\p{Lu}+)(\p{Lu}\p{Ll})", "$1 $2");
        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

Blog Post Referenced


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

我知道一定有一个简单的正则表达式方法...我得开始更多地使用它。 - Max Schmeling
1
我不是正则表达式专家,但是 "HeresAWTFString" 会发生什么? - Nick
1
你得到了“Heres A W T F String”,但这正是Matias Nino在问题中要求的。 - Max Schmeling
是的,他需要添加“多个相邻的大写字母保持不变”的功能。在许多情况下,这显然是必需的,例如“PublisherID”会被转换为“Publisher I D”,这样做非常糟糕。 - PandaWood

2

正则表达式比简单循环慢约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();
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接