.NET - "Büyük harf" ile ayrılmış bir dizeyi bir diziye nasıl bölebilirsiniz?


114

Bu dizeden nasıl giderim: "ThisIsMyCapsDelimitedString"

... şu dizeye: "Bu Benim Büyük Harfle Ayrılmış Dizim"

VB.net'te en az sayıda kod satırı tercih edilir, ancak C # da kabul edilir.

Şerefe!


1
"OldMacDonaldAndMrO'TooleWentToMcDonalds" ile uğraşmanız gerektiğinde ne olur?
Grant Wagner

2
Sadece sınırlı kullanım görecek. Bunu esas olarak ThisIsMySpecialVariable gibi değişken adlarını çözümlemek için kullanacağım
Matias Nino

Bu benim için çalıştı: Regex.Replace(s, "([A-Z0-9]+)", " $1").Trim(). Ve her büyük harfe bölmek istiyorsanız, artı işaretini kaldırmanız yeterlidir.
Mladen B.

Yanıtlar:


173

Bunu bir süre önce yaptım. Bir CamelCase adının her bir bileşeniyle eşleşir.

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

Örneğin:

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

Bunu kelimeler arasına boşluk eklemeye dönüştürmek için:

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

Rakamları işlemeniz gerekiyorsa:

/([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! Adı buydu! Onu seviyorum! Çok teşekkürler!
Matias Nino

19
Aslında camelCase'in başında küçük harf vardır. Burada bahsettiğiniz şey PascalCase'dir.
Drew Noakes

12
... ve "deve vakası" veya "pascal vakası" olabilecek bir şeye atıfta bulunduğunuzda, buna "ara kapaklı" denir
Chris

Kullanım durumumda başarısız olacak "Take5" i
bölmez

1
@PandaWood Digits soruda yoktu, bu yüzden cevabım onları açıklamadı. Rakamları açıklayan modellerin bir çeşidini ekledim.
Markus Jarderot

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

Şimdiye kadarki en iyi çözüm budur, ancak derlemek için \\ B kullanmanız gerekir. Aksi takdirde, derleyici \ B'yi bir kaçış dizisi olarak ele almaya çalışır.
Ferruccio

Güzel çözüm. Bunun kabul edilen cevap olmaması için bir neden düşünebilen var mı? Daha az yetenekli mi yoksa daha az performanslı mı?
Drew Noakes

8
Bu, ardışık büyük harfleri ayrı kelimeler olarak ele alır (örneğin, ANZAC 5 kelimedir) ve MizardX'ın cevabı onu (doğru şekilde IMHO) tek kelime olarak ele alır.
Ray

2
@Ray, "ANZAC" kelimesinin "Anzac" olarak yazılması gerektiğini savunuyorum çünkü bu kelime İngilizce değil.
Sam

1
@Neaox, İngilizce olması gerekir, ancak bu kısaltma durumu veya normal-ingilizce-durum değil; büyük harfle sınırlandırılmıştır. Kaynak metin normal İngilizcede olduğu gibi büyük harfle yazılmalıysa, diğer harfler de büyük yazılmamalıdır. Örneğin, "ANZAC" ta "NZAC" yerine büyük harfle ayrılmış biçime sığması için "i" in "büyük harfle yazılmalıdır? Açıkçası, "ANZAC" kelimesini büyük harfle ayrılmış olarak yorumlarsanız, her harf için bir tane olmak üzere 5 kelimedir.
Sam

19

Harika cevap, MizardX! Rakamları ayrı kelimeler olarak ele almak için biraz ince ayar yaptım, böylece "Adres Satırı1", "Adres Satırı1" yerine "Adres Satırı 1" olacaktı:

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

2
Harika bir ek! Kabul edilen cevabın dizelerdeki sayıları işlemesine birkaç kişinin şaşırmayacağından şüpheleniyorum. :)
Jordan Grey

Bunu yayınladığınızdan bu yana neredeyse 8 yıl geçtiğini biliyorum, ama benim için de mükemmel çalıştı. :) Rakamlar ilk başta beni şaşırttı.
Michael Armes

2 aykırı değer testimi geçen tek yanıt: "Take5" -> "5 Al", "Yayıncı Kimliği" -> "Yayıncı Kimliği". Buna iki kez olumlu oy vermek istiyorum
PandaWood

18

Sadece biraz çeşitlilik için ... İşte normal ifade kullanmayan bir uzatma yöntemi.

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 () kullanmaktan kaçınmak için foreach öncesinde şunu koydum: int counter = -1. içeriye counter ++ ekleyin. kontrolü şu şekilde değiştirin: if (char.IsUpper (c) && counter> 0)
Kutunun Dışında Geliştirici

Bu, 1. karakterden önce bir boşluk ekler.
Zar Shardan

@ZarShardan tarafından işaret edilen sorunu çözme özgürlüğünü aldım. Değişiklikten hoşlanmıyorsanız, geri dönüp kendi düzeltmenizi düzeltmekten çekinmeyin.
jpmc26

Bu, örneğin bir büyük harf dizisinde son büyük harften önce bir boşluk ekleyerek kısaltmaları işleyecek şekilde geliştirilebilir mi, örneğin BOEForecast => BOE Forecast
Nepaluz

11

Grant Wagner'in mükemmel yorumu bir yana:

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

İyi nokta ... Lütfen istediğiniz .substring (), .trimstart (), .trim (), .remove (), vb. Ekleyin. :)
Sözde Mazoşist

9

Kısaltmaları ve sayıları destekleyen bir çözüme ihtiyacım vardı. Bu Regex tabanlı çözüm, aşağıdaki kalıpları ayrı "kelimeler" olarak ele alır:

  • Büyük harf ve ardından küçük harfler
  • Bir dizi ardışık sayı
  • Ardışık büyük harfler (kısaltma olarak yorumlanır) - yeni bir kelime son büyük harf kullanılarak başlayabilir, örneğin HTMLGuide => "HTML Rehberi", "TheATeam" => "A Takımı"

Sen olabilir tek astar olarak bunu:

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

Daha okunaklı bir yaklaşım daha iyi olabilir:

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

İşte (XUnit) testlerinden bir alıntı:

[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
Normal ifadeyi açıklamak ve bu kadar okunabilir hale getirmek için + 1. Ve yeni bir şey öğrendim. .NET Regex'te bir serbest aralık modu ve açıklamalar vardır. Teşekkür ederim!
Felix Keil

4

Daha fazla çeşitlilik için, düz eski C # nesnelerini kullanarak, aşağıdakiler @ MizardX'in mükemmel düzenli ifadesi ile aynı çıktıyı üretir.

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
Vay canına, bu çirkin. Şimdi regex'i neden bu kadar çok sevdiğimi hatırlıyorum! Yine de çaba için +1. ;)
Mark Brackett

3

Aşağıda, aşağıdakileri Title Case'e dönüştüren bir prototip bulunmaktadır:

  • snake_case
  • camelCase
  • PascalCase
  • cümle davası
  • Başlık Örneği (mevcut biçimlendirmeyi koruyun)

Açıkçası, yalnızca "ToTitleCase" yöntemine kendiniz ihtiyacınız olacak.

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

Konsolun çıkışı aşağıdaki gibi olacaktır:

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

Başvurulan Blog Gönderisi


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

Kolay bir RegEx yolu olacağını biliyordum ... Daha çok kullanmaya başlamalıyım.
Max Schmeling

1
Bir normal ifade uzmanı değil ama "HeresAWTFString" ile ne olur?
Nick

1
"Heres AWTF String" alırsınız, ancak Matias Nino'nun sorduğu tam olarak buydu.
Max Schmeling

Evet, "birden fazla bitişik başkentin yalnız kaldığını" eklemesi gerekiyor. Pek çok durumda oldukça açık bir şekilde gerekli olan, örneğin "PublisherID" burada "Publisher I D" ye gidiyor ki bu korkunç
PandaWood

2

Normal ifade, basit bir döngüden yaklaşık 10-12 kat daha yavaştır:

    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

Saf normal ifade çözümü. O'Conner'ı işlemez ve dizenin başına da bir boşluk ekler.

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

Seni modifiye ettim, ancak insanlar genellikle "saf" ile başlamazsa daha iyi dövüşür.
MusiGenesis

Bunun bir şaplak olduğunu sanmıyorum. Bu bağlamda, naif, genellikle açık veya basit anlamına gelir (yani, en iyi çözüm olması gerekmez). Hakaret niyeti yoktur.
Ferruccio

0

Muhtemelen daha şık bir çözüm var, ama aklıma gelen şu:

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

Kullanmaya çalışmak

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

Sonuç, rakamlarla alfabe karışımına uyacak

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

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

0

Psudo kodunu şuradan uygulayın: 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

Prosedürel ve hızlı uygulama:

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

Testler:

  [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

Özellikle girdi dizesinin boyutu büyüdükçe, bir regex çözümünden daha hızlı sipariş (ler) olması gereken basit bir çözüm (bu ileti dizisindeki en iyi çözümlere karşı yaptığım testlere dayanarak):

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();
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.