.NET-「キャップ」で区切られた文字列を配列に分割するにはどうすればよいですか?


114

「ThisIsMyCapsDelimitedString」という文字列からどうやって行くのですか?

...この文字列へ:「This Is My Caps Delimited String」

VB.netのコードは最小限の行が推奨されますが、C#も使用できます。

乾杯!


1
「OldMacDonaldAndMrO'TooleWentToMcDonalds」に対処しなければならない場合はどうなりますか?
グラントワグナー

2
使用が制限されるだけです。私は主に、ThisIsMySpecialVariableなどの変数名を解析するためにそれを使用するだけです
Matias Nino

これは私のために働きました:Regex.Replace(s, "([A-Z0-9]+)", " $1").Trim()。また、大文字で分割する場合は、プラス記号を削除するだけです。
Mladen B.

回答:


173

私はこれを少し前に作りました。キャメルケース名の各コンポーネントと一致します。

/([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

19
実際、キャメルケースには小文字が先行しています。ここで言及しているのはPascalCaseです。
Drew Noakes、

12
...「キャメルケース」や「パスカルケース」の可能性があるものを指す場合、「インターキャップ」と呼ばれます
Chris

「Take5」を分割しないため、ユースケースに失敗します
PandaWood 2017年

1
@PandaWood Digitsは問題ではなかったので、私の答えはそれらを考慮していませんでした。数字を表すパターンのバリアントを追加しました。
Markus Jarderot 2017年

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

これはこれまでのところ最良の解決策ですが、コンパイルするには\\ Bを使用する必要があります。それ以外の場合、コンパイラは\ Bをエスケープシーケンスとして扱います。
Ferruccio

素晴らしい解決策。これが受け入れられるべき答えではない理由を誰かが考えることができますか?機能が低下している、またはパフォーマンスが低下していますか
Drew Noakes、2010

8
これは、連続した大文字を別々の単語として扱います(たとえば、ANZACは5単語です)。MizardXの回答では、それを(正しくはIMHO)1つの単語として扱います。
Ray

2
@Ray、私は "ANZAC"は英語の大文字ではないので "Anzac"と書く必要があると主張します。
Sam

1
@Neaox、英語であるはずですが、これは頭字語の場合や通常の英語の場合ではありません。大文字で区切られています。ソーステキストを通常の英語と同じように大文字にする必要がある場合は、他の文字も大文字にしないでください。たとえば、「is」の「i」は大文字で区切られた形式に合わせて大文字にする必要がありますが、「ANZAC」の「NZAC」は大文字にしないのはなぜですか?厳密に言えば、「ANZAC」を大文字で区切られたものとして解釈すると、各文字に1つずつ、合計5ワードになります。
サム

19

すばらしい答え、MizardX!「AddressLine1」が「Address Line1」ではなく「Address Line 1」になるように、数字を個別の単語として扱うように少し調整しました。

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

2
素晴らしい追加!受け入れられた回答の文字列内の数値の処理に驚く人は少なくないでしょう。:)
ジョーダングレイ

あなたがこれを投稿してからほぼ8年になりますが、私にとっても完全に機能しました。:)最初は数字がつまずきました。
Michael Armes

私の2つの外れ値テストに合格する唯一の答え:「Take5」->「Take 5」、「PublisherID」->「Publisher ID」。これを2回
賛成し

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)&&カウンタ> 0)
ボックスの開発者以外で

これにより、最初の文字の前にスペースが挿入されます。
Zar Shardan 2017年

私は、@ ZarShardanによって指摘された問題を自由に修正しました。変更が気に入らない場合は、自由にロールバックするか、独自の修正に編集してください。
jpmc26

これは、たとえば、一連の大文字の最後の大文字の前にスペースを追加するなど、略語を処理するように拡張できますか。例:BOEForecast => BOE Forecast
Nepaluz

11

グラント・ワーグナーの素晴らしいコメントはさておき:

Dim s As String = RegularExpressions.Regex.Replace("ThisIsMyCapsDelimitedString", "([A-Z])", " $1")

良い点...お好きな.substring()、. trimstart()、. trim()、. remove()などを自由に挿入してください。:)
疑似マゾヒスト

9

頭字語と数字をサポートするソリューションが必要でした。この正規表現ベースのソリューションは、次のパターンを個々の「単語」として扱います。

  • 大文字の後に小文字
  • 一連の連続番号
  • 連続した大文字(頭字語として解釈される)-新しい単語は最後の大文字を使用して開始できます。例:HTMLGuide => "HTML Guide"、 "TheATeam" => "The A Team"

あなたワンライナーとしてそれを行うことができます:

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、2015

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
うわー、それは醜いです。今、私はなぜ正規表現が大好きなのかを覚えています!努力のために+1。;)
Mark Brackett

3

以下は、次のものをタイトルケースに変換するプロトタイプです。

  • snake_case
  • キャメルケース
  • パスカルケース
  • 文例
  • タイトルケース(現在のフォーマットを維持)

言うまでもなく、 "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

参照されたブログ投稿


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

簡単なRegExの方法があることを知っていました...もっと使い始めなければなりません。
Max Schmeling

1
正規表現の第一人者ではありませんが、「HeresAWTFString」で何が起こりますか?
ニック

1
"Heres AWTF 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();
    }

1

素朴な正規表現ソリューション。O'Connerを処理せず、文字列の先頭にもスペースを追加します。

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

私はあなたを改造しましたが、それが「ナイーブ」で始まらない場合、人々は一般にスマックダウンをよりよくします。
MusiGenesis 2008

それはスマックダウンではなかったと思います。この文脈では、ナイーブは通常、明白または単純であることを意味します(つまり、必ずしも最良の解決策ではない)。侮辱するつもりはありません。
Ferruccio

0

おそらくもっと洗練された解決策がありますが、これは私が頭の上から思いついたものです:

string myString = "ThisIsMyCapsDelimitedString";

for (int i = 1; i < myString.Length; i++)
{
     if (myString[i].ToString().ToUpper() == myString[i].ToString())
     {
          myString = myString.Insert(i, " ");
          i++;
     }
}

0

使ってみる

"([A-Z]*[^A-Z]*)"

結果は、数字とアルファベットの組み合わせに適合します

Regex.Replace("AbcDefGH123Weh", "([A-Z]*[^A-Z]*)", "$1 ");
Abc Def GH123 Weh  

Regex.Replace("camelCase", "([A-Z]*[^A-Z]*)", "$1 ");
camel Case  

0

https://stackoverflow.com/a/5796394/4279201からのpsudoコードの実装:

    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;
    }


0

手続き的で高速な実装:

  /// <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));
  }

0

特に入力文字列のサイズが大きくなると、(このスレッドの上位ソリューションに対して実行したテストに基づいて)正規表現ソリューションよりも桁違いに高速な単純なソリューション:

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();
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.