.NET - Làm cách nào bạn có thể chia một chuỗi được phân tách bằng "mũ" thành một mảng?


114

Làm cách nào để truy cập chuỗi này: "ThisIsMyCapsDelimitedString"

... tới chuỗi này: "Đây là chuỗi được phân cách bằng Caps của tôi"

Ít dòng mã nhất trong VB.net được ưu tiên nhưng C # cũng được hoan nghênh.

Chúc mừng!


1
Điều gì xảy ra khi bạn phải đối phó với "OldMacDonaldAndMrO'TooleWentToMcDonalds"?
Grant Wagner

2
Nó sẽ chỉ được sử dụng hạn chế. Tôi sẽ chủ yếu chỉ được sử dụng nó để phân tích tên biến như ThisIsMySpecialVariable,
Matias Nino

Điều này làm việc cho tôi: Regex.Replace(s, "([A-Z0-9]+)", " $1").Trim(). Và nếu bạn muốn tách trên mỗi chữ cái viết hoa, chỉ cần bỏ dấu cộng.
Mladen B.

Câu trả lời:


173

Tôi đã làm điều này một thời gian trước đây. Nó khớp với từng thành phần của tên CamelCase.

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

Ví dụ:

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

Để chuyển đổi điều đó thành chỉ chèn khoảng trắng giữa các từ:

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

Nếu bạn cần xử lý các chữ số:

/([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! Đó là những gì nó được gọi! Tôi thích nó! Cảm ơn nhiều!
Matias Nino

19
Trên thực tế camelCase có một chữ cái thường ở đầu. Những gì bạn đang đề cập ở đây là PascalCase.
Drew Noakes

12
... và khi bạn đề cập đến một cái gì đó có thể là "vỏ lạc đà" hoặc "hộp pascal", nó được gọi là "xen kẽ"
Chris

Không chia "Take5" mà sẽ thất bại trường hợp sử dụng của tôi
PandaWood

1
@PandaWood Digits không có trong câu hỏi, vì vậy câu trả lời của tôi không giải thích cho chúng. Tôi đã thêm một biến thể của các mẫu có chứa chữ số.
Markus Jarderot

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

Đây là giải pháp tốt nhất cho đến nay, nhưng bạn cần sử dụng \\ B để biên dịch. Nếu không, trình biên dịch cố gắng coi \ B là một chuỗi thoát.
Ferruccio 01-08

Giải pháp tốt. Bất cứ ai có thể nghĩ ra một lý do mà đây không phải là câu trả lời được chấp nhận? Nó có khả năng kém hơn hoặc hiệu suất kém hơn?
Drew Noakes

8
Câu trả lời này coi chữ viết hoa liên tiếp là các từ riêng biệt (ví dụ: ANZAC là 5 từ) trong đó câu trả lời của MizardX coi nó (đúng IMHO) là một từ.
Ray

2
@Ray, tôi lập luận rằng "ANZAC" nên được viết thành "Anzac" để được coi là một từ viết hoa pascal vì nó không phải là chữ viết hoa tiếng Anh.
Sam

1
@Neaox, trong tiếng Anh thì nên như vậy, nhưng đây không phải là từ viết tắt-viết hoa hay bình thường-tiếng Anh-viết thường; nó được phân cách bằng chữ hoa. Nếu văn bản nguồn phải được viết hoa giống như trong tiếng Anh thông thường, thì các chữ cái khác cũng không được viết hoa. Ví dụ: tại sao "i" trong "là" phải được viết hoa để phù hợp với định dạng phân cách bằng chữ hoa nhưng không phải là "NZAC" trong "ANZAC"? Nói một cách chính xác, nếu bạn giải thích "ANZAC" là phân tách bằng chữ hoa thì nó có 5 từ, mỗi từ một chữ cái.
Sam

19

Câu trả lời tuyệt vời, MizardX! Tôi đã chỉnh sửa một chút để coi các chữ số là các từ riêng biệt, để "AddressLine1" sẽ trở thành "Address Line 1" thay vì "Address Line1":

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

2
Bổ sung tuyệt vời! Tôi nghi ngờ rằng không ít người sẽ ngạc nhiên bởi việc xử lý các số trong chuỗi được chấp nhận của câu trả lời. :)
Jordan Grey

Tôi biết đã gần 8 năm kể từ khi bạn đăng bài này, nhưng nó cũng hoạt động hoàn hảo đối với tôi. :) Những con số khiến tôi vấp ngã lúc đầu.
Michael Armes

Câu trả lời duy nhất vượt qua 2 bài kiểm tra ngoại lệ của tôi: "Take5" -> "Take 5", "PublisherID" -> "Publisher ID". Tôi muốn upvote này hai lần
PandaWood

18

Chỉ cho một chút đa dạng ... Đây là một phương pháp mở rộng không sử dụng 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;
        }
    }
}

Để tránh sử dụng Trim (), trước foreach tôi đặt: int counter = -1. bên trong, thêm bộ đếm ++. thay đổi séc thành: if (char.IsUpper (c) && counter> 0)
Bên ngoài Nhà phát triển Hộp

Điều này sẽ chèn một khoảng trắng trước ký tự đầu tiên.
Zar Shardan

Tôi đã tự do khắc phục sự cố do @ZarShardan chỉ ra. Vui lòng quay lại hoặc chỉnh sửa thành bản sửa lỗi của riêng bạn nếu bạn không thích thay đổi.
jpmc26

Điều này có thể được tăng cường để xử lý chữ viết tắt ví dụ bằng cách thêm dấu cách trước chữ hoa cuối cùng trong một loạt các chữ cái in hoa ví dụ BOEForecast => Dự báo BOE
Nepaluz

11

Nhận xét tuyệt vời của Grant Wagner sang một bên:

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

Điểm tốt ... Vui lòng chèn .substring (), .trimstart (), .trim (), .remove (), v.v. mà bạn chọn. :)
Pseudo Masochist

9

Tôi cần một giải pháp hỗ trợ từ viết tắt và số. Giải pháp dựa trên Regex này coi các mẫu sau là "từ" riêng lẻ:

  • Một chữ cái viết hoa theo sau là chữ cái thường
  • Một dãy số liên tiếp
  • Các chữ cái viết hoa liên tiếp (được hiểu là từ viết tắt) - một từ mới có thể bắt đầu sử dụng chữ hoa cuối cùng, ví dụ: HTMLGuide => "Hướng dẫn HTML", "TheATeam" => "Nhóm A"

Bạn có thể làm điều đó như một lớp lót:

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

Một cách tiếp cận dễ đọc hơn có thể tốt hơn:

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

Đây là phần trích xuất từ ​​các bài kiểm tra (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 để giải thích regex và làm cho nó có thể đọc được. Và tôi đã học được một điều mới. Có một chế độ khoảng cách trống và nhận xét trong .NET Regex. Cảm ơn bạn!
Felix Keil

4

Để đa dạng hơn, sử dụng các đối tượng C # cũ thuần túy, phần sau tạo ra kết quả giống như biểu thức chính quy tuyệt vời của @ 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
Chà, thật là xấu xí. Bây giờ tôi nhớ tại sao tôi rất yêu regex! +1 cho nỗ lực, mặc dù. ;)
Mark Brackett

3

Dưới đây là một nguyên mẫu chuyển đổi những thứ sau thành Title Case:

  • con rắn
  • camelCase
  • PascalCase
  • trường hợp câu
  • Chữ hoa tiêu đề (giữ nguyên định dạng hiện tại)

Rõ ràng là bạn sẽ chỉ cần phương thức "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);
    }
}

Giao diện điều khiển sẽ như sau:

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

Bài đăng trên Blog được tham chiếu


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

Tôi biết sẽ có một cách RegEx dễ dàng ... Tôi phải bắt đầu sử dụng nó nhiều hơn.
Max Schmeling 30/09/08

1
Không phải là chuyên gia regex nhưng điều gì sẽ xảy ra với "HeresAWTFString"?
Nick

1
Bạn nhận được "Đây là Chuỗi AWTF" nhưng đó chính xác là những gì Matias Nino yêu cầu trong câu hỏi.
Max Schmeling 30/09/08

Vâng, anh ấy cần phải thêm rằng "nhiều thủ đô liền kề được để riêng". Đó là khá rõ ràng là cần thiết trong nhiều trường hợp ví dụ: "PublisherID" ở đây đi vào "Nhà xuất bản Tôi D", đó là khủng khiếp
PandaWood

2

Regex chậm hơn khoảng 10-12 lần so với một vòng lặp đơn giản:

    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

Giải pháp regex ngây thơ. Sẽ không xử lý O'Conner và cũng thêm một khoảng trắng ở đầu chuỗi.

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

Tôi đã sửa đổi cho bạn, nhưng mọi người thường đánh bại tốt hơn nếu nó không bắt đầu bằng "ngây thơ".
MusiGenesis 30/09/08

Tôi không nghĩ rằng đó là một cuộc tấn công. Trong bối cảnh này, ngây thơ thường có nghĩa là hiển nhiên hoặc đơn giản (tức là không nhất thiết phải là giải pháp tốt nhất). Không có ý định xúc phạm.
Ferruccio

0

Có lẽ có một giải pháp thanh lịch hơn, nhưng đây là những gì tôi nghĩ ra trên đỉnh đầu của mình:

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

Thử sử dụng

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

Kết quả sẽ phù hợp với hỗn hợp bảng chữ cái với số

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

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

0

Triển khai mã psudo từ: 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

Thủ tục và cấy ghép nhanh chóng:

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

Kiểm tra:

  [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

Một giải pháp đơn giản, phải có (các) thứ tự cường độ nhanh hơn so với giải pháp regex (dựa trên các thử nghiệm mà tôi đã chạy so với các giải pháp hàng đầu trong chủ đề này), đặc biệt khi kích thước của chuỗi đầu vào tăng lên:

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();
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.