2011-11-14 42 views
5

Tôi muốn chia chuỗi thành danh sách hoặc mảng.Cách chia chuỗi bằng cách sử dụng cụm từ thông dụng

Input: green,"yellow,green",white,orange,"blue,black"

Nhân vật chia là dấu phẩy (,), nhưng nó phải bỏ qua dấu phẩy bên trong dấu ngoặc kép.

Sản lượng nên là:

  • xanh
  • vàng, xanh lá cây
  • trắng
  • cam
  • xanh, đen

Cảm ơn.

+0

Bạn đang cần thiết để sử dụng regex? – rownage

+0

hui, Hãy bình luận, b) upvote và c) chọn câu trả lời giúp bạn nhiều nhất. :) –

Trả lời

11

Trên thực tế điều này là đủ dễ dàng để chỉ cần sử dụng phù hợp:

 string subjectString = @"green,""yellow,green"",white,orange,""blue,black"""; 
     try 
     { 
      Regex regexObj = new Regex(@"(?<="")\b[a-z,]+\b(?="")|[a-z]+", RegexOptions.IgnoreCase); 
      Match matchResults = regexObj.Match(subjectString); 
      while (matchResults.Success) 
      { 
       Console.WriteLine("{0}", matchResults.Value); 
       // matched text: matchResults.Value 
       // match start: matchResults.Index 
       // match length: matchResults.Length 
       matchResults = matchResults.NextMatch(); 
      } 

Output:

green 
yellow,green 
white 
orange 
blue,black 

Giải thích:

@" 
      # Match either the regular expression below (attempting the next alternative only if this one fails) 
    (?<=   # Assert that the regex below can be matched, with the match ending at this position (positive lookbehind) 
     ""   # Match the character “""” literally 
    ) 
    \b   # Assert position at a word boundary 
    [a-z,]  # Match a single character present in the list below 
        # A character in the range between “a” and “z” 
        # The character “,” 
     +   # Between one and unlimited times, as many times as possible, giving back as needed (greedy) 
    \b   # Assert position at a word boundary 
    (?=   # Assert that the regex below can be matched, starting at this position (positive lookahead) 
     ""   # Match the character “""” literally 
    ) 
|   # Or match regular expression number 2 below (the entire match attempt fails if this one fails to match) 
    [a-z]  # Match a single character in the range between “a” and “z” 
     +   # Between one and unlimited times, as many times as possible, giving back as needed (greedy) 
" 
+0

@downvoter Bạn có quan tâm giải thích lý do đằng sau sự sụt giảm của bạn không? Hoặc bạn sẽ tiếp tục ẩn đằng sau giấu tên của bạn? – FailedDev

+1

LOL đầu tiên chấp nhận câu trả lời có downvote. Có huy hiệu cho việc này không? : D – FailedDev

+0

Tôi không nghĩ rằng Cụm từ thông dụng là cách để đi với vấn đề này vì tất cả các chuyển tiếp về phía trước và sau đó đều rất chậm. Nhưng tôi sẽ cho bạn một cuộc bỏ phiếu để giải quyết ví dụ đơn giản này. –

2

Ai đó sẽ sớm đưa ra câu trả lời thực hiện điều này với một regex duy nhất. Tôi không thông minh, nhưng chỉ vì lợi ích của sự cân bằng, đây là một gợi ý mà không sử dụng một regex hoàn toàn. Dựa trên câu ngạn ngữ cũ khi bạn cố gắng giải quyết vấn đề với regex, bạn sẽ gặp phải hai vấn đề. :)

Cá nhân cho tôi thiếu regex-fu, tôi muốn làm một trong các cách sau:

  • Sử dụng đơn giản regex dựa trên Replace để thoát khỏi bất kỳ dấu phẩy bên dấu ngoặc kép với cái gì khác (ví dụ: "&comma;"). Sau đó, bạn có thể làm một đơn giản string.Split() trên kết quả và unescape mỗi mục trong mảng kết quả trước khi bạn sử dụng nó. Đây là yucky. Một phần vì nó xử lý mọi thứ, và một phần vì nó cũng sử dụng các regex. Boooo!
  • Phân tích cú pháp bằng tay, char bằng char. Chuyển đổi chuỗi thành một mảng char, sau đó lặp qua nó, lưu ý xem bạn có phải là "dấu ngoặc kép bên trong" hay không, và xây dựng mảng kết quả một char tại một thời điểm.
  • Tương tự như đề xuất trước, nhưng sử dụng csv-parser từ một người nào đó trên internet. Ví dụ tôi tạo dưới đây không chính xác vượt qua tất cả các bài kiểm tra từ đặc tả csv, vì vậy nó chỉ thực sự là một hướng dẫn để minh họa quan điểm của tôi.

Có một cơ hội tốt về các tùy chọn không phải regex sẽ hoạt động tốt hơn nếu được viết tốt bởi vì regex có thể tốn kém một chút khi quét chuỗi nội bộ tìm kiếm mẫu.

Thực sự, tôi chỉ muốn chỉ ra rằng bạn không phải sử dụng regex. :)

Đây là một triển khai khá ngây thơ về đề xuất thứ hai của tôi. Trên PC của tôi, nó phân tích cú pháp 1 triệu chuỗi 15 cột trong vòng hơn 4,5 giây.

public class ManualParser : IParser 
{ 
    public IEnumerable<string> Parse(string line) 
    { 
     if (string.IsNullOrWhiteSpace(line)) return new List<string>(); 

     line = line.Trim(); 

     if (line.Contains(",") == false) return new[] { line.Trim('"') }; 

     if (line.Contains("\"") == false) return line.Split(',').Select(c => c.Trim()); 

     bool withinQuotes = false; 
     var builder = new List<string>(); 
     var trimChars = new[] { ' ', '"' }; 

     int left = 0; 
     int right = 0; 

     for (right = 0; right < line.Length; right++) 
     { 
      char c = line[right]; 

      if (c == '"') 
      { 
       withinQuotes = !withinQuotes; 
       continue; 
      } 

      if (c == ',' && !withinQuotes) 
      { 
       builder.Add(line.Substring(left, right - left).Trim(trimChars)); 
       right++; // Jump the comma 
       left = right; 
      } 
     } 

     builder.Add(line.Substring(left, right - left).Trim(trimChars)); 

     return builder; 
    } 
} 

Dưới đây là một số xét nghiệm đơn vị cho nó:

[TestFixture] 
public class ManualParserTests 
{ 
    [Test] 
    public void Parse_GivenStringWithNoQuotesAndNoCommas_ShouldReturnThatString() 
    { 
     // Arrange 
     var parser = new ManualParser(); 

     // Act 
     string[] result = parser.Parse("This is my data").ToArray(); 

     // Assert 
     Assert.AreEqual(1, result.Length, "Should only be one column returned"); 
     Assert.AreEqual("This is my data", result[0], "Incorrect value is returned"); 
    } 

    [Test] 
    public void Parse_GivenStringWithNoQuotesAndOneComma_ShouldReturnTwoColumns() 
    { 
     // Arrange 
     var parser = new ManualParser(); 

     // Act 
     string[] result = parser.Parse("This is, my data").ToArray(); 

     // Assert 
     Assert.AreEqual(2, result.Length, "Should be 2 columns returned"); 
     Assert.AreEqual("This is", result[0], "First value is incorrect"); 
     Assert.AreEqual("my data", result[1], "Second value is incorrect"); 
    } 

    [Test] 
    public void Parse_GivenStringWithQuotesAndNoCommas_ShouldReturnColumnWithoutQuotes() 
    { 
     // Arrange 
     var parser = new ManualParser(); 

     // Act 
     string[] result = parser.Parse("\"This is my data\"").ToArray(); 

     // Assert 
     Assert.AreEqual(1, result.Length, "Should be 1 column returned"); 
     Assert.AreEqual("This is my data", result[0], "Value is incorrect"); 
    } 

    [Test] 
    public void Parse_GivenStringWithQuotesAndCommas_ShouldReturnColumnsWithoutQuotes() 
    { 
     // Arrange 
     var parser = new ManualParser(); 

     // Act 
     string[] result = parser.Parse("\"This is\", my data").ToArray(); 

     // Assert 
     Assert.AreEqual(2, result.Length, "Should be 2 columns returned"); 
     Assert.AreEqual("This is", result[0], "First value is incorrect"); 
     Assert.AreEqual("my data", result[1], "Second value is incorrect"); 
    } 

    [Test] 
    public void Parse_GivenStringWithQuotesContainingCommasAndCommas_ShouldReturnColumnsWithoutQuotes() 
    { 
     // Arrange 
     var parser = new ManualParser(); 

     // Act 
     string[] result = parser.Parse("\"This, is\", my data").ToArray(); 

     // Assert 
     Assert.AreEqual(2, result.Length, "Should be 2 columns returned"); 
     Assert.AreEqual("This, is", result[0], "First value is incorrect"); 
     Assert.AreEqual("my data", result[1], "Second value is incorrect"); 
    } 
} 

Và đây là một ứng dụng mẫu mà tôi đã thử nghiệm thông với:

class Program 
{ 
    static void Main(string[] args) 
    { 
     RunTest(); 
    } 

    private static void RunTest() 
    { 
     var parser = new ManualParser(); 
     string csv = Properties.Resources.Csv; 
     var result = new StringBuilder(); 
     var s = new Stopwatch(); 

     for (int test = 0; test < 3; test++) 
     { 
      int lineCount = 0; 

      s.Start(); 
      for (int i = 0; i < 1000000/50; i++) 
      { 
       foreach (var line in csv.Split(new[] { Environment.NewLine }, StringSplitOptions.None)) 
       { 
        string cur = line + s.ElapsedTicks.ToString(); 
        result.AppendLine(parser.Parse(cur).ToString()); 
        lineCount++; 
       } 
      } 
      s.Stop(); 
      Console.WriteLine("Completed {0} lines in {1}ms", lineCount, s.ElapsedMilliseconds); 
      s.Reset(); 
      result = new StringBuilder(); 
     } 
    } 
} 
2

Định dạng của chuỗi bạn đang cố gắng chia có vẻ là CSV chuẩn. Sử dụng trình phân tích cú pháp CSV có thể sẽ dễ dàng hơn/nhanh hơn.

5

Những gì bạn có có một ngôn ngữ bất quy tắc. Nói cách khác, ý nghĩa của một nhân vật phụ thuộc vào chuỗi ký tự trước hoặc sau ký tự đó. Như tên ngụ ý các biểu thức chính quy là để phân tích cú pháp các ngôn ngữ thông thường.

Những gì bạn cần ở đây là TokenizerParser, một công cụ tìm kiếm trên internet tốt sẽ hướng dẫn bạn làm ví dụ. Thực tế là các mã thông báo chỉ là các ký tự mà bạn có thể thậm chí không cần Tokenizer.

Mặc dù bạn có thể thực hiện trường hợp đơn giản này bằng cách sử dụng Cụm từ thông dụng, nhưng nó rất chậm. Nó cũng có thể gây ra vấn đề nếu bao giờ các dấu ngoặc kép không cân bằng như là một biểu thức chính quy sẽ không phát hiện lỗi này, nơi như một phân tích cú pháp sẽ.

Nếu bạn đang nhập tệp CSV, bạn có thể muốn xem lớp Microsoft.VisualBasic.FileIO.TextFieldParser (Chỉ cần thêm tham chiếu đến Microsoft.VisualBasic.dll trong dự án C#) phân tích tệp CSV.

Một cách khác để làm điều này là để viết riêng state machine (ví dụ dưới đây) của bạn mặc dù điều này vẫn không giải quyết vấn đề của một báo giá ở giữa một giá trị:

using System; 
using System.Text; 

namespace Example 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      string subjectString = @"green,""yellow,green"",white,orange,""blue,black"""; 

      bool inQuote = false; 
      StringBuilder currentResult = new StringBuilder(); 
      foreach (char c in subjectString) 
      { 
       switch (c) 
       { 
        case '\"': 
         inQuote = !inQuote; 
         break; 

        case ',': 
         if (inQuote) 
         { 
          currentResult.Append(c); 
         } 
         else 
         { 
          Console.WriteLine(currentResult); 
          currentResult.Clear(); 
         } 
         break; 

        default: 
         currentResult.Append(c); 
         break; 
       } 
      } 
      if (inQuote) 
      { 
       throw new FormatException("Input string does not have balanced Quote Characters"); 
      } 
      Console.WriteLine(currentResult); 
     } 
    } 
} 
Các vấn đề liên quan