2012-12-17 26 views
8

.NET cung cấp bộ sưu tập Capture trong triển khai RegularExpression của nó để bạn có thể nhận được tất cả các trường hợp của một nhóm lặp nhất định chứ không phải là phiên bản cuối cùng của nó. Điều đó thật tuyệt, nhưng tôi có một nhóm lặp lại với các nhóm con và tôi đang cố gắng truy cập các nhóm con khi chúng có liên quan trong nhóm và không thể tìm thấy cách nào. Bất kỳ đề xuất?Trong RegEx của .NET tôi có thể lấy một bộ sưu tập Nhóm từ một đối tượng Capture không?

tôi đã xem xét số câu hỏi khác, ví dụ:

nhưng tôi đã không tìm thấy câu trả lời áp dụng một trong hai khẳng định ("Yep, đây là cách ") hoặc tiêu cực (" Không, không thể làm được. ").

Đối với một ví dụ contrived nói rằng tôi có một chuỗi đầu vào:

abc d x 1 2 x 3 x 5 6 e fgh 

nơi "abc" và "FGH" đại diện cho văn bản mà tôi muốn bỏ qua trong tài liệu lớn hơn, "d" và "e" bao quanh khu vực quan tâm và trong khu vực quan tâm đó, "xn [n]" có thể lặp lại bất kỳ số lần nào. Đó là những cặp số trong các lĩnh vực "x" Tôi quan tâm đến

Vì vậy, tôi phân tích nó bằng mẫu biểu thức chính quy này:.

.*d (?<x>x ((?<fir>\d+))?((?<sec>\d+))?)*?e.* 

mà sẽ tìm thấy chính xác một trận đấu trong tài liệu, nhưng nắm bắt nhóm "x" nhiều lần. Dưới đây là ba cặp tôi muốn trích xuất trong ví dụ này:

  • 1, 2
  • 5, 6

nhưng làm thế nào tôi có thể nhận được chúng? Tôi có thể làm như sau (trong C#):

using System; 
using System.Text; 
using System.Text.RegularExpressions; 

string input = "abc d x 1 2 x 3 x 5 6 e fgh"; 
string pattern = @".*d (?<x>x ((?<fir>\d+))?((?<sec>\d+))?)*?e.*"; 
foreach (var x in Regex.Match(input, pattern).Groups["x"].Captures) { 
    MessageBox.Show(x.ToString()); 
} 

và kể từ khi tôi đang tham khảo nhóm "x" Tôi có được những chuỗi:

  • x 1 2
  • x 3
  • x 5 6

Nhưng điều đó không giúp tôi có được con số.Vì vậy, tôi có thể làm "linh sam" và "giây" độc lập thay vì chỉ "x":

using System; 
using System.Text; 
using System.Text.RegularExpressions; 

string input = "abc d x 1 2 x 3 x 5 6 e fgh"; 
string pattern = @".*d (?<x>x ((?<fir>\d+))?((?<sec>\d+))?)*?e.*"; 
Match m = Regex.Match(input, pattern); 
foreach (var f in m.Groups["fir"].Captures) { 
    MessageBox.Show(f.ToString()); 
} 

foreach (var s in m.Groups["sec"].Captures) { 
    MessageBox.Show(s.ToString()); 
} 

để có được:

nhưng sau đó tôi không có cách nào để biết rằng đó là cặp thứ hai thiếu "4", và không phải là một trong các cặp khác.

Vậy phải làm gì? Tôi biết tôi có thể dễ dàng phân tích cú pháp này trong C# hoặc thậm chí với thử nghiệm regex thứ hai trên nhóm "x", nhưng kể từ lần chạy RegEx đầu tiên đã thực hiện tất cả công việc và kết quả ĐƯỢC biết, có vẻ như phải có cách để điều khiển đối tượng Match để có được thứ tôi cần. Và hãy nhớ, đây là một ví dụ giả tạo, trường hợp thế giới thực có phần phức tạp hơn vì vậy chỉ cần ném thêm mã C# vào nó sẽ là một nỗi đau. Nhưng nếu các đối tượng .NET hiện tại không thể làm được, thì tôi chỉ cần biết điều đó và tôi sẽ tiếp tục theo cách của mình.

Suy nghĩ?

+0

Xuất hiện không có khái niệm về phân cấp trong tên. Tôi sẽ chỉ làm một Regex thứ hai trên x làm nhận được linh sam. – Paparazzi

Trả lời

5

Tôi không biết về giải pháp được xây dựng hoàn chỉnh và không thể tìm thấy giải pháp sau khi tìm kiếm nhanh, nhưng điều này không loại trừ khả năng có một.

Đề xuất tốt nhất của tôi là sử dụng các thuộc tính IndexLength để tìm các ảnh chụp phù hợp. Nó có vẻ không thực sự thanh lịch nhưng bạn có thể có thể đưa ra một số mã khá tốt đẹp sau khi viết một số phương pháp mở rộng.

var input = "abc d x 1 2 x 3 x 5 6 e fgh"; 

var pattern = @".*d (?<x>x ((?<fir>\d+))?((?<sec>\d+))?)*?e.*"; 

var match = Regex.Match(input, pattern); 

var xs = match.Groups["x"].Captures.Cast<Capture>(); 

var firs = match.Groups["fir"].Captures.Cast<Capture>(); 
var secs = match.Groups["sec"].Captures.Cast<Capture>(); 

Func<Capture, Capture, Boolean> test = (inner, outer) => 
    (inner.Index >= outer.Index) && 
    (inner.Index < outer.Index + outer.Length); 

var result = xs.Select(x => new 
          { 
           Fir = firs.FirstOrDefault(f => test(f, x)), 
           Sec = secs.FirstOrDefault(s => test(s, x)) 
          }) 
       .ToList(); 

Đây là một giải pháp có thể sử dụng phương pháp mở rộng sau.

internal static class Extensions 
{ 
    internal static IEnumerable<Capture> GetCapturesInside(this Match match, 
     Capture capture, String groupName) 
    { 
     var start = capture.Index; 
     var end = capture.Index + capture.Length; 

     return match.Groups[groupName] 
        .Captures 
        .Cast<Capture>() 
        .Where(inner => (inner.Index >= start) && 
            (inner.Index < end)); 
    } 
} 

Bây giờ bạn có thể viết lại mã như sau.

var input = "abc d x 1 2 x 3 x 5 6 e fgh"; 

var pattern = @".*d (?<x>x ((?<fir>\d+))?((?<sec>\d+))?)*?e.*"; 

var match = Regex.Match(input, pattern); 

foreach (Capture x in match.Groups["x"].Captures) 
{ 
    var fir = match.GetCapturesInside(x, "fir").SingleOrDefault(); 
    var sec = match.GetCapturesInside(x, "sec").SingleOrDefault(); 
} 
+0

Rực rỡ. Phục vụ mục đích một cách tao nhã và hiệu quả. Bằng chứng rằng nếu "Match.Group.Capture.Group" không có trong .NET, nó phải là. Cảm ơn! – bob

+0

@ user1910619 Tôi trân trọng không đồng ý ... xem câu trả lời của tôi cho vấn đề. – OmegaMan

3

Nó sẽ luôn luôn là một cặp so với duy nhất? Bạn có thể sử dụng các nhóm chụp riêng biệt. Tất nhiên, bạn mất thứ tự các mục với phương pháp này.

var input = "abc d x 1 2 x 3 x 5 6 e fgh"; 
var re = new Regex(@"d\s(?<x>x\s((?<pair>\d+\s\d+)|(?<single>\d+))\s)*e"); 

var m = re.Match(input); 
foreach (Capture s in m.Groups["pair"].Captures) 
{ 
    Console.WriteLine(s.Value); 
} 
foreach (Capture s in m.Groups["single"].Captures) 
{ 
    Console.WriteLine(s.Value); 
} 

1 2 
5 6 
3 

Nếu bạn cần đơn hàng, tôi có thể đi theo gợi ý của Blam để sử dụng cụm từ thông dụng thứ hai.

2

Tôi khuyên bạn nên xem xét độc đáo đối với .net regex Nhóm cân bằng.

Dưới đây là một regex sử dụng để dừng trận đấu khi nhóm (không phải là chữ số hoặc chữ X) được tìm thấy để đóng nhóm. Sau đó, các kết quả phù hợp được truy cập thông qua các ảnh chụp theo yêu cầu:

string data = "abc d x 1 2 x 3 x 5 6 e fgh"; 

string pattern = 
@"(?xn) # Specify options in the pattern 
      # x - to comment (IgnorePatternWhitespace) 
      # n - Explicit Capture to ignore non named matches 

(?<X>x)     # Push the X on the balanced group 
    ((\s)(?<Numbers>\d+))+ # Load up on any numbers into the capture group 
(?(Paren)(?!))    # Stop any match that has an X 
          #(the end of the balance group)"; 


var results = Regex.Matches(data, pattern) 
        .OfType<Match>() 
        .Select ((mt, index) => string.Format("Match {0}: {1}", 
              index, 
              string.Join(", ", 
                 mt.Groups["Numbers"] 
                 .Captures 
                 .OfType<Capture>() 
                 .Select (cp => cp.Value)))) 
        ; 

results.ToList() 
     .ForEach(result => Console.WriteLine (result)); 
/* Output 

Match 0: 1, 2 
Match 1: 3 
Match 2: 5, 6 

*/ 
+0

Đây là một giải pháp thông minh cho câu hỏi mà tôi đã hỏi, cảm ơn bạn. Thật không may, đối với trường hợp thế giới thực của tôi, các nhóm con được đặt tên không giống nhau, chúng có các mẫu độc lập riêng liên kết với tên của chúng. Cuối cùng, giải pháp phải ở trong mã chứ không phải là mẫu regex. – bob

+0

@bob Tôi chỉ làm việc với ví dụ bạn đưa ra, nếu mẫu có các nhóm con khác nhau, thì hệ thống các nhóm balanace phù hợp cũng có thể được áp dụng cho các nhóm con hoặc một mệnh đề nếu mệnh đề có thể xử lý các nhóm dữ liệu độc lập tùy thuộc vào nhu cầu. – OmegaMan

+0

Hm, tôi đoán tôi không hiểu gì ở đây; trong mọi trường hợp không phải là giải pháp phải đến từ mã .NET và không phải từ các biểu thức chính quy cao cấp hơn? Tôi không thể thấy làm thế nào chúng ta có thể thay đổi regex để kéo các kết quả tương tự ("1,2", "3", "4,5") ra khỏi một cái gì đó phức tạp hơn, như 'ab x id: 7 val: 8 cdx khác: 9 id: 1 khác: 10 val: 2 otherjunk x id: 3 x val: 6 id: 5 e fgh'. Và thậm chí nếu chúng ta có thể, sự phức tạp của regex có lẽ sẽ vượt xa nhu cầu, đặc biệt là khi câu trả lời đã được nạp vào đối tượng Match từ trận đấu regex gốc đơn giản, và chỉ cần truy cập bằng cách nào đó. – bob

1

Tôi đã xem câu trả lời của OmegaMan và biết rằng bạn thích mã C# thay vì giải pháp regex. Nhưng tôi cũng muốn trình bày một cách khác.

Trong .NET, bạn có thể sử dụng lại các nhóm được đặt tên.Mỗi khi một cái gì đó được chụp với nhóm đó, nó được đẩy lên ngăn xếp (đó là những gì mà OmegaMan đề cập đến bằng cách "cân bằng nhóm"). Bạn có thể sử dụng để thúc đẩy một chụp rỗng vào stack cho mỗi x bạn tìm thấy:

string pattern = @"d (?<x>x(?<d>) (?:(?<d>\d+))*)*e"; 

Vì vậy, bây giờ sau khi phù hợp với x các (?<d>) đẩy một chụp rỗng vào stack. Đây là Console.WriteLine đầu ra (một dòng cho mỗi capture):

  
1 
2 

3 

5 
6 

Do đó, khi bạn sau đó đi bộ qua Regex.Match(input, pattern).Groups["d"].Captures và ghi chuỗi rỗng, bạn biết rằng một nhóm mới của số đã bắt đầu.

+0

Ah, tôi đã không nhận ra rằng bạn có thể tái sử dụng các nhóm được đặt tên với các mẫu khác nhau, tôi có thể thấy cách này có thể hoạt động. Thông tin hữu ích! Tôi đã không giới hạn bản thân mình với giải pháp C#, chỉ cần nhận ra rằng nếu .NET không cung cấp cách để thu thập các nhóm trong một capture, một số mã C# đặc biệt chắc chắn sẽ là cần thiết (như trong trường hợp này, hãy để trống giá trị chụp). Tôi vẫn thích giải pháp @Daniel được cung cấp. Bên cạnh đó là phổ biến độc đáo, tôi thấy nó giữ sự phức tạp của mô hình regex cân xứng hơn với độ phức tạp của đầu vào. Cảm ơn mặc dù! – bob

Các vấn đề liên quan