.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!


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

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

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

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

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

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")]

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

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


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

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

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]", " $&");

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

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  

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


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

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();
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.