.NET - Bagaimana Anda bisa membagi string yang dipisahkan "huruf besar" menjadi sebuah array?

114

Bagaimana cara beralih dari string ini: "ThisIsMyCapsDelimitedString"

... ke string ini: "This Is My Caps Delimited String"

Baris kode paling sedikit di VB.net lebih disukai tetapi C # juga diterima.

Bersulang!

Matias Nino
sumber
1
Apa yang terjadi jika Anda harus berurusan dengan "OldMacDonaldAndMrO'TooleWentToMcDonalds"?
Grant Wagner
2
Ini hanya akan melihat penggunaan terbatas. Saya terutama akan menggunakannya untuk mengurai nama variabel seperti ThisIsMySpecialVariable,
Matias Nino
Ini bekerja untuk saya: Regex.Replace(s, "([A-Z0-9]+)", " $1").Trim(). Dan jika Anda ingin membagi setiap huruf kapital, hapus saja plusnya.
Mladen B.

Jawaban:

173

Saya membuat ini beberapa waktu lalu. Ini cocok dengan setiap komponen dari nama CamelCase.

/([A-Z]+(?=$|[A-Z][a-z])|[A-Z]?[a-z]+)/g

Sebagai contoh:

"SimpleHTTPServer" => ["Simple", "HTTP", "Server"]
"camelCase" => ["camel", "Case"]

Untuk mengubahnya menjadi hanya menyisipkan spasi di antara kata-kata:

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

Jika Anda perlu menangani angka:

/([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 ")
Markus Jarderot
sumber
1
CamelCase! Itulah namanya! Aku menyukainya! Terimakasih banyak!
Matias Nino
19
Sebenarnya camelCase memiliki huruf kecil di depannya. Yang Anda maksud di sini adalah PascalCase.
Drew Noakes
12
... dan jika Anda merujuk pada sesuatu yang bisa berupa "kasus unta" atau "kasus pascal", ini disebut "intercapped"
Chris
Tidak membagi "Take5" yang akan gagal dalam kasus penggunaan saya
PandaWood
1
@PandaWood Digit tidak ada dalam pertanyaan, jadi jawaban saya tidak memperhitungkannya. Saya telah menambahkan varian pola yang memperhitungkan digit.
Markus Jarderot
36
Regex.Replace("ThisIsMyCapsDelimitedString", "(\\B[A-Z])", " $1")
Wayne
sumber
Sejauh ini, ini adalah solusi terbaik, tetapi Anda perlu menggunakan \\ B untuk mengkompilasi. Jika tidak, kompilator mencoba memperlakukan \ B sebagai urutan pelolosan.
Ferruccio
Solusi bagus. Adakah yang bisa memikirkan alasan mengapa ini seharusnya tidak menjadi jawaban yang diterima? Apakah itu kurang mampu atau kurang berkinerja?
Drew Noakes
8
Yang satu ini memperlakukan huruf besar yang berurutan sebagai kata yang terpisah (misalnya ANZAC terdiri dari 5 kata) sedangkan jawaban MizardX memperlakukannya (dengan benar IMHO) sebagai satu kata.
Ray
2
@ Ray, saya berpendapat bahwa "ANZAC" harus ditulis sebagai "Anzac" untuk dianggap sebagai kata kasus pascal karena ini bukan kasus bahasa Inggris.
Sam
1
@ Neox, seharusnya dalam bahasa Inggris, tetapi ini bukan akronim-case atau normal-english-case; itu dibatasi huruf besar. Jika teks sumber harus menggunakan huruf besar dengan cara yang sama seperti dalam bahasa Inggris normal, maka huruf lain juga tidak boleh menggunakan huruf besar. Misalnya, mengapa "i" dalam "adalah" menggunakan huruf besar agar sesuai dengan format yang dipisahkan huruf kapital tetapi bukan "NZAC" dalam "ANZAC"? Sebenarnya, jika Anda menafsirkan "ANZAC" sebagai huruf besar-kecil maka itu adalah 5 kata, satu untuk setiap huruf.
Sam
19

Jawaban yang bagus, MizardX! Saya mengubahnya sedikit untuk memperlakukan angka sebagai kata terpisah, sehingga "AddressLine1" akan menjadi "Address Line 1" bukan "Address Line1":

Regex.Replace(s, "([a-z](?=[A-Z0-9])|[A-Z](?=[A-Z][a-z]))", "$1 ")
JoshL
sumber
2
Tambahan yang bagus! Saya menduga tidak sedikit orang akan terkejut dengan jawaban yang diterima penanganan bilangan dalam string. :)
Jordan Grey
Saya tahu sudah hampir 8 tahun sejak Anda memposting ini, tetapi ini juga berfungsi dengan baik untuk saya. :) Angka-angka itu membuat saya tersandung pada awalnya.
Michael Armes
Satu-satunya jawaban yang lolos dari 2 tes pencilan saya: "Take5" -> "Take 5", "PublisherID" -> "Publisher ID". Saya ingin memberi
suara positif
18

Hanya untuk sedikit variasi ... Berikut adalah metode ekstensi yang tidak menggunakan regex.

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;
        }
    }
}
Troy Howard
sumber
Untuk menghindari penggunaan Trim (), sebelum foreach saya meletakkan: int counter = -1. di dalam, tambahkan counter ++. ubah centang ke: if (char.IsUpper (c) && counter> 0)
Outside the Box Developer
Ini menyisipkan spasi sebelum karakter pertama.
Zar Shardan
Saya telah mengambil kebebasan untuk memperbaiki masalah yang ditunjukkan oleh @ZarShardan. Jangan ragu untuk memutar kembali atau mengedit perbaikan Anda sendiri jika Anda tidak menyukai perubahan tersebut.
jpmc26
Dapatkah ini ditingkatkan untuk menangani singkatan misalnya dengan menambahkan spasi sebelum huruf besar terakhir dalam serangkaian huruf besar misalnya BOEForecast => BOE Forecast
Nepaluz
11

Berikan komentar luar biasa Wagner:

Dim s As String = RegularExpressions.Regex.Replace("ThisIsMyCapsDelimitedString", "([A-Z])", " $1")
Masokis Semu
sumber
Poin bagus ... Jangan ragu untuk memasukkan .substring (), .trimstart (), .trim (), .remove (), dll. Sesuai pilihan Anda. :)
Pseudo Masochist
9

Saya membutuhkan solusi yang mendukung akronim dan angka. Solusi berbasis Regex ini memperlakukan pola berikut sebagai "kata" individual:

  • Huruf kapital diikuti dengan huruf kecil
  • Urutan angka berurutan
  • Huruf kapital berturut-turut (diartikan sebagai akronim) - kata baru dapat dimulai menggunakan huruf besar terakhir, misalnya HTMLGuide => "Panduan HTML", "TheATeam" => "Tim A"

Anda bisa melakukannya sebagai satu baris:

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

Pendekatan yang lebih mudah dibaca mungkin lebih baik:

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

Berikut ekstrak dari tes (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")]
Dan Malcolm
sumber
1
+1 untuk menjelaskan regex dan membuatnya dapat dibaca. Dan saya belajar sesuatu yang baru. Ada mode spasi bebas dan komentar di .NET Regex. Terima kasih!
Felix Keil
4

Untuk variasi lainnya, menggunakan objek C # biasa, berikut ini menghasilkan output yang sama seperti ekspresi reguler @ MizardX yang sangat baik.

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();
}
Robert Paulson
sumber
2
Wow, jelek sekali. Sekarang saya ingat mengapa saya sangat menyukai regex! 1 untuk usaha. ;)
Mark Brackett
3

Di bawah ini adalah prototipe yang mengubah yang berikut ini menjadi Kasus Judul:

  • snake_case
  • camelCase
  • PascalCase
  • kasus kalimat
  • Judul Kasus (pertahankan format saat ini)

Jelas Anda hanya membutuhkan metode "ToTitleCase" sendiri.

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

Konsol keluarnya adalah sebagai berikut:

THE Quick Brown Fox
The QUICK Brown Fox
The Quick Brown FOX
The Quick Brown Fox
The Quick Brown Fox
The FOX
FOX
QUICK

Entri Blog Dirujuk

Brantley Blanchard
sumber
2
string s = "ThisIsMyCapsDelimitedString";
string t = Regex.Replace(s, "([A-Z])", " $1").Substring(1);
Ferruccio
sumber
Saya tahu akan ada cara RegEx yang mudah ... Saya harus mulai lebih sering menggunakannya.
Max Schmeling
1
Bukan guru regex tapi apa yang terjadi dengan "HeresAWTFString"?
Nick
1
Anda mendapatkan "Heres AWTF String" tapi itulah yang ditanyakan Matias Nino dalam pertanyaan tersebut.
Max Schmeling
Ya, dia perlu menambahkan bahwa "beberapa ibu kota yang berdekatan dibiarkan sendiri". Yang cukup jelas diperlukan dalam banyak kasus misalnya "PublisherID" di sini menuju ke "Publisher I D" yang sangat buruk
PandaWood
2

Regex sekitar 10-12 kali lebih lambat dari loop sederhana:

    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();
    }
Zar Shardan
sumber
1

Solusi regex naif. Tidak akan menangani O'Conner, dan menambahkan spasi di awal string juga.

s = "ThisIsMyCapsDelimitedString"
split = Regex.Replace(s, "[A-Z0-9]", " $&");
Geoff
sumber
Saya memodifikasi Anda, tetapi orang-orang biasanya akan lebih baik jika tidak dimulai dengan "naif".
MusiGenesis
Saya tidak berpikir itu adalah pukulan keras. Dalam konteks ini, naif biasanya berarti jelas atau sederhana (yaitu belum tentu solusi terbaik). Tidak ada niat menghina.
Ferruccio
0

Mungkin ada solusi yang lebih elegan, tetapi inilah yang saya temukan di luar kepala saya:

string myString = "ThisIsMyCapsDelimitedString";

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

Coba gunakan

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

Hasilnya akan cocok untuk campuran alfabet dengan angka

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

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

Menerapkan kode psudo dari: 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;
    }
shinzou
sumber
0

Impl prosedural dan cepat:

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

Tes:

  [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));
  }
Patrick dari tim NDepend
sumber
0

Solusi sederhana, yang harus urutan besarnya lebih cepat daripada solusi regex (berdasarkan tes yang saya lakukan terhadap solusi teratas di utas ini), terutama saat ukuran string input bertambah:

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();
iliketocode.dll
sumber