Làm cách nào để đối sánh a ^ nb ^ n với Java regex?


99

Đây là phần thứ hai của loạt bài báo về giáo dục regex. Nó cho biết cách sử dụng các lookahead và các tham chiếu lồng nhau để so khớp với uuge a n b n không thường xuyên . Tham chiếu lồng nhau lần đầu tiên được giới thiệu trong: Làm thế nào để regex này tìm các số tam giác?

Một trong những ngôn ngữ không thông thường nguyên mẫu là:

L = { an bn: n > 0 }

Đây là ngôn ngữ của tất cả các chuỗi không rỗng bao gồm một số ký tự a'theo sau là một số ký tự b' bằng nhau . Ví dụ về chuỗi trong ngôn ngữ này là ab, aabb, aaabbb.

Ngôn ngữ này có thể được chứng minh là không thường xuyên theo bổ đề bơm . Trên thực tế, nó là một ngôn ngữ không có ngữ cảnh nguyên mẫu , có thể được tạo ra bởi ngữ pháp không có ngữ cảnh S → aSb | ab .

Tuy nhiên, các triển khai regex ngày nay rõ ràng nhận ra nhiều thứ hơn là chỉ các ngôn ngữ thông thường. Đó là, chúng không "chính quy" theo định nghĩa lý thuyết ngôn ngữ chính thức. PCRE và Perl hỗ trợ regex đệ quy và .NET hỗ trợ định nghĩa nhóm cân bằng. Ngay cả các tính năng ít "lạ mắt" hơn, ví dụ như kết hợp tham chiếu ngược, có nghĩa là regex không thường xuyên.

Nhưng tính năng "cơ bản" này mạnh đến mức nào? LVí dụ, chúng ta có thể nhận ra với Java regex không? Chúng ta có thể có lẽ kết hợp lookarounds và tài liệu tham khảo lồng nhau và có mẫu có thể làm việc với ví dụ như String.matchesđể phù hợp với chuỗi như ab, aabb, aaabbb, vv?

Người giới thiệu

Câu hỏi được liên kết


4
Loạt bài này được bắt đầu bởi sự cho phép của một số người trong cộng đồng ( meta.stackexchange.com/questions/62695/… ). Nếu việc tiếp nhận tốt, tôi dự định sẽ tiếp tục trình bày các tính năng nâng cao hơn cũng như cơ bản hơn của regex.
polygenelubricants


Chà, tôi chưa bao giờ biết các regex của Java sẽ không bị giới hạn ở các biểu thức chính quy. Tôi đoán điều đó giải thích tại sao tôi luôn nghĩ rằng chúng sẽ không được triển khai đầy đủ. Ý tôi là không có toán tử bổ sung, khác biệt hoặc sản phẩm nào được tích hợp trong Java Regex, nhưng điều đó có ý nghĩa vì chúng không bị giới hạn ở Ngôn ngữ thông thường.
Lan

Câu hỏi này đã được thêm vào Câu hỏi thường gặp về Cụm từ Thông dụng Stack Overflow , trong "Advanced Regex-Fu".
aliteralmind

Câu trả lời:


139

Câu trả lời là, không cần phải nói, CÓ! Chắc chắn bạn có thể viết một mẫu regex Java để khớp với một n b n . Nó sử dụng một cái nhìn tích cực để khẳng định và một tham chiếu lồng nhau để "đếm".

Thay vì đưa ra mô hình ngay lập tức, câu trả lời này sẽ hướng dẫn người đọc trong quá trình tìm ra nó. Nhiều gợi ý khác nhau được đưa ra khi giải pháp được xây dựng chậm. Ở khía cạnh này, hy vọng câu trả lời này sẽ chứa đựng nhiều thứ hơn là chỉ một mẫu regex gọn gàng khác. Hy vọng rằng độc giả cũng sẽ học được cách "suy nghĩ trong regex", và cách kết hợp các cấu trúc khác nhau một cách hài hòa với nhau, để họ có thể tự tìm ra nhiều mẫu hơn trong tương lai.

Ngôn ngữ được sử dụng để phát triển giải pháp sẽ là PHP vì tính ngắn gọn của nó. Kiểm tra cuối cùng sau khi hoàn tất mẫu sẽ được thực hiện trong Java.


Bước 1: Nhìn trước để khẳng định

Hãy bắt đầu với một vấn đề đơn giản hơn: chúng ta muốn so khớp a+ở đầu một chuỗi, nhưng chỉ khi nó được theo sau ngay lập tức b+. Chúng ta có thể sử dụng ^để neo trận đấu của chúng tôi, và vì chúng tôi chỉ muốn để phù hợp với a+mà không có b+, chúng ta có thể sử dụng lookahead khẳng định (?=…).

Đây là mẫu của chúng tôi với một dây nịt thử nghiệm đơn giản:

function testAll($r, $tests) {
   foreach ($tests as $test) {
      $isMatch = preg_match($r, $test, $groups);
      $groupsJoined = join('|', $groups);
      print("$test $isMatch $groupsJoined\n");
   }
}
 
$tests = array('aaa', 'aaab', 'aaaxb', 'xaaab', 'b', 'abbb');
 
$r1 = '/^a+(?=b+)/';
#          └────┘
#         lookahead

testAll($r1, $tests);

Đầu ra là ( như đã thấy trên ideone.com ):

aaa 0
aaab 1 aaa
aaaxb 0
xaaab 0
b 0
abbb 1 a

Đây chính xác là kết quả chúng tôi muốn: chúng tôi khớp a+, chỉ khi nó ở đầu chuỗi và chỉ khi nó ngay sau đó b+.

Bài học : Bạn có thể sử dụng các mẫu trong cách xem xét để đưa ra khẳng định.


Bước 2: Chụp ở chế độ nhìn trước (và chế độ giãn cách tự do)

Bây giờ chúng ta hãy nói rằng mặc dù chúng tôi không muốn b+trở thành một phần của trận đấu, chúng tôi muốn nắm bắt nó anyway vào nhóm 1. Ngoài ra, như chúng tôi dự kiến có một mô hình phức tạp hơn, chúng ta hãy sử dụng của xmodifier cho tự do khoảng cách vì vậy chúng tôi có thể làm cho regex của chúng tôi dễ đọc hơn.

Dựa trên đoạn mã PHP trước đây của chúng tôi, bây giờ chúng tôi có mẫu sau:

$r2 = '/ ^ a+ (?= (b+) ) /x';
#                └──┘ 
#                  1  
#             └────────┘
#              lookahead
 
testAll($r2, $tests);

Kết quả bây giờ là ( như đã thấy trên ideone.com ):

aaa 0
aaab 1 aaa|b
aaaxb 0
xaaab 0
b 0
abbb 1 a|bbb

Lưu ý rằng ví dụ: aaa|blà kết quả của join-ing những gì mỗi nhóm được chụp '|'. Trong trường hợp này, nhóm 0 (tức là mẫu phù hợp) được chụp aaavà nhóm 1 được chụp b.

Bài học : Bạn có thể nắm bắt bên trong một cái nhìn bao quát. Bạn có thể sử dụng khoảng cách trống để nâng cao khả năng đọc.


Bước 3: Cấu trúc lại lookahead vào "vòng lặp"

Trước khi có thể giới thiệu cơ chế đếm của mình, chúng ta cần thực hiện một sửa đổi đối với mẫu của mình. Hiện tại, lookahead nằm ngoài +"vòng lặp" lặp lại. Điều này là tốt cho đến nay bởi vì chúng tôi chỉ muốn khẳng định rằng có một số b+theo dõi của chúng tôi a+, nhưng những gì chúng tôi thực sự muốn làm cuối cùng là khẳng định rằng đối với mỗi acái mà chúng tôi khớp bên trong "vòng lặp", sẽ có một phần tương ứng bđi kèm với nó.

Bây giờ đừng lo lắng về cơ chế đếm và chỉ cần thực hiện cấu trúc lại như sau:

  • Cơ cấu lại đầu tiên a+cho (?: a )+(lưu ý rằng đó (?:…)là một nhóm không nắm bắt)
  • Sau đó di chuyển hướng nhìn vào bên trong nhóm không chụp này
    • Lưu ý rằng bây giờ chúng ta phải "bỏ qua" a*trước khi có thể "nhìn thấy" b+, vì vậy hãy sửa đổi mẫu cho phù hợp

Vì vậy, bây giờ chúng ta có những thứ sau:

$r3 = '/ ^ (?: a (?= a* (b+) ) )+ /x';
#                     └──┘  
#                       1   
#               └───────────┘ 
#                 lookahead   
#          └───────────────────┘
#           non-capturing group

Đầu ra vẫn giống như trước đây ( như đã thấy trên ideone.com ), vì vậy không có thay đổi về vấn đề đó. Điều quan trọng là bây giờ chúng ta đang thực hiện khẳng định tại mỗi lần lặp của +"vòng lặp". Với mẫu hiện tại của chúng tôi, điều này là không cần thiết, nhưng tiếp theo chúng tôi sẽ đặt "tính" nhóm 1 cho chúng tôi bằng cách sử dụng tự tham chiếu.

Bài học : Bạn có thể chụp bên trong một nhóm không chụp. Các cách nhìn có thể được lặp lại.


Bước 4: Đây là bước mà chúng ta bắt đầu đếm

Đây là những gì chúng tôi sẽ làm: chúng tôi sẽ viết lại nhóm 1 sao cho:

  • Vào cuối lần lặp đầu tiên của +, khi lần đầu tiên ađược so khớp, nó sẽ nắm bắtb
  • Vào cuối lần lặp thứ hai, khi một lần lặp khác ađược so khớp, nó sẽ nắm bắtbb
  • Vào cuối lần lặp thứ ba, nó sẽ nắm bắt bbb
  • ...
  • Vào cuối lần lặp thứ n , nhóm 1 sẽ nắm bắt được b n
  • Nếu không có đủ bđể nắm bắt vào nhóm 1 thì xác nhận đơn giản là không thành công

Vì vậy, nhóm 1, bây giờ (b+), sẽ phải được viết lại thành một cái gì đó giống như (\1 b). Đó là, chúng tôi cố gắng "thêm" a bvào nhóm 1 đã được chụp trong lần lặp trước.

Có một vấn đề nhỏ ở đây là mẫu này thiếu "trường hợp cơ sở", tức là trường hợp mà nó có thể khớp mà không cần tự tham chiếu. Một trường hợp cơ sở là bắt buộc vì nhóm 1 bắt đầu "chưa được khởi tạo"; nó vẫn chưa nắm bắt bất kỳ thứ gì (thậm chí không phải là một chuỗi trống), vì vậy nỗ lực tự tham chiếu sẽ luôn thất bại.

Có nhiều cách giải quyết vấn đề này, nhưng bây giờ chúng ta hãy chỉ làm cho đối sánh tự tham chiếu là tùy chọn , tức là \1?. Điều này có thể hoạt động hoàn hảo hoặc có thể không hoàn hảo, nhưng chúng ta hãy xem điều đó có tác dụng gì, và nếu có bất kỳ vấn đề gì thì chúng ta sẽ vượt qua cây cầu đó khi chúng ta đến với nó. Ngoài ra, chúng tôi sẽ thêm một số trường hợp thử nghiệm khác trong khi chúng tôi đang ở đó.

$tests = array(
  'aaa', 'aaab', 'aaaxb', 'xaaab', 'b', 'abbb', 'aabb', 'aaabbbbb', 'aaaaabbb'
);
 
$r4 = '/ ^ (?: a (?= a* (\1? b) ) )+ /x';
#                     └─────┘ | 
#                        1    | 
#               └──────────────┘ 
#                   lookahead    
#          └──────────────────────┘
#             non-capturing group

Kết quả bây giờ là ( như đã thấy trên ideone.com ):

aaa 0
aaab 1 aaa|b        # (*gasp!*)
aaaxb 0
xaaab 0
b 0
abbb 1 a|b          # yes!
aabb 1 aa|bb        # YES!!
aaabbbbb 1 aaa|bbb  # YESS!!!
aaaaabbb 1 aaaaa|bb # NOOOOOoooooo....

A-ha! Có vẻ như bây giờ chúng ta đã thực sự gần đến giải pháp! Chúng tôi đã quản lý để đưa nhóm 1 "đếm" bằng cách sử dụng tự tham chiếu! Nhưng khoan đã ... có gì đó không ổn với trường hợp thử nghiệm thứ hai và cuối cùng !! Không có đủ bs, và bằng cách nào đó nó được tính sai! Chúng tôi sẽ xem xét lý do tại sao điều này xảy ra trong bước tiếp theo.

Bài học : Một cách để "khởi tạo" nhóm tự tham chiếu là làm cho đối sánh tự tham chiếu là tùy chọn.


Bước 4½: Tìm hiểu vấn đề đã xảy ra

Vấn đề là vì chúng tôi đã thực hiện tùy chọn đối sánh tự tham chiếu, nên "bộ đếm" có thể "đặt lại" về 0 khi không có đủ b. Hãy kiểm tra chặt chẽ những gì xảy ra ở mỗi lần lặp lại mẫu của chúng ta với aaaaabbbđầu vào.

 a a a a a b b b

# Initial state: Group 1 is "uninitialized".
           _
 a a a a a b b b
  
  # 1st iteration: Group 1 couldn't match \1 since it was "uninitialized",
  #                  so it matched and captured just b
           ___
 a a a a a b b b
    
    # 2nd iteration: Group 1 matched \1b and captured bb
           _____
 a a a a a b b b
      
      # 3rd iteration: Group 1 matched \1b and captured bbb
           _
 a a a a a b b b
        
        # 4th iteration: Group 1 could still match \1, but not \1b,
        #  (!!!)           so it matched and captured just b
           ___
 a a a a a b b b
          
          # 5th iteration: Group 1 matched \1b and captured bb
          #
          # No more a, + "loop" terminates

A-ha! Ở lần lặp thứ 4, chúng tôi vẫn có thể khớp \1, nhưng chúng tôi không thể khớp \1b! Vì chúng tôi cho phép đối sánh tự tham chiếu là tùy chọn \1?, động cơ sẽ lùi lại và sử dụng tùy chọn "không, cảm ơn", sau đó cho phép chúng tôi đối sánh và nắm bắt b!

Tuy nhiên, hãy lưu ý rằng ngoại trừ lần lặp đầu tiên, bạn luôn có thể khớp chỉ với tự tham chiếu \1. Tất nhiên, điều này là hiển nhiên, vì đó là những gì chúng tôi vừa chụp được trong lần lặp trước đó và trong thiết lập của mình, chúng tôi luôn có thể khớp lại nó (ví dụ: nếu chúng tôi đã chụp bbblần trước, chúng tôi đảm bảo rằng sẽ vẫn có bbb, nhưng có thể hoặc có thể không phải là bbbblúc này).

Bài học : Cẩn thận với việc bẻ khóa. Công cụ regex sẽ thực hiện việc bẻ khóa ngược nhiều như bạn cho phép cho đến khi mẫu nhất định khớp. Điều này có thể ảnh hưởng đến hiệu suất (tức là nứt ngược thảm khốc ) và / hoặc tính đúng đắn.


Bước 5: Tự sở hữu để giải cứu!

"Sửa chữa" bây giờ nên rõ ràng: kết hợp lặp lại tùy chọn với định lượng sở hữu . Đó là, thay vì chỉ đơn giản ?, hãy sử dụng ?+thay thế (hãy nhớ rằng sự lặp lại được định lượng là sở hữu không có tác dụng ngược, ngay cả khi sự "hợp tác" như vậy có thể dẫn đến sự trùng khớp của mẫu tổng thể).

Theo các thuật ngữ rất thân mật, đây là những gì ?+, ???nói:

?+

  • (tùy chọn) "Nó không cần phải ở đó,"
    • (sở hữu) "nhưng nếu nó ở đó, bạn phải nắm lấy nó và không được buông ra!"

?

  • (tùy chọn) "Nó không cần phải ở đó,"
    • (tham lam) "nhưng nếu có, bạn có thể lấy nó ngay bây giờ,"
      • (backtracking) "nhưng bạn có thể được yêu cầu bỏ qua sau!"

??

  • (tùy chọn) "Nó không cần phải ở đó,"
    • (miễn cưỡng) "và ngay cả khi đó là bạn vẫn chưa cần phải lấy nó"
      • (backtracking) "nhưng bạn có thể được yêu cầu lấy nó sau!"

Trong thiết lập của chúng tôi, \1sẽ không có ngay lần đầu tiên, nhưng nó sẽ luôn ở đó bất cứ lúc nào sau đó và chúng tôi luôn muốn khớp với nó sau đó. Do đó, \1?+sẽ đạt được chính xác những gì chúng ta muốn.

$r5 = '/ ^ (?: a (?= a* (\1?+ b) ) )+ /x';
#                     └──────┘  
#                         1     
#               └───────────────┘ 
#                   lookahead     
#          └───────────────────────┘
#             non-capturing group

Bây giờ đầu ra là ( như đã thấy trên ideone.com ):

aaa 0
aaab 1 a|b          # Yay! Fixed!
aaaxb 0
xaaab 0
b 0
abbb 1 a|b
aabb 1 aa|bb
aaabbbbb 1 aaa|bbb
aaaaabbb 1 aaa|bbb  # Hurrahh!!!

Voilà !!! Vấn đề đã được giải quyết !!! Bây giờ chúng tôi đang đếm đúng, chính xác theo cách chúng tôi muốn!

Bài học : Tìm hiểu sự khác biệt giữa sự lặp lại tham lam, miễn cưỡng và chiếm hữu. Sở hữu tùy chọn có thể là một sự kết hợp mạnh mẽ.


Bước 6: Hoàn thiện các bước chạm

Vì vậy, những gì chúng ta có ngay bây giờ là một mẫu khớp alặp đi lặp lại và đối với mỗi mẫu ađã khớp, sẽ có một mẫu tương ứng bđược bắt trong nhóm 1. Mẫu +chấm dứt khi không còn mẫu nào nữa ahoặc nếu xác nhận không thành công vì không có mẫu tương ứng bcho một a.

Để hoàn thành công việc, chúng ta chỉ cần thêm vào mẫu của chúng ta \1 $. Đây bây giờ là tham chiếu ngược về nhóm 1 đã khớp, tiếp theo là ký tự neo cuối dòng. Mỏ neo đảm bảo rằng không có bất kỳ phần thừa nào btrong chuỗi; nói cách khác, trong thực tế, chúng ta có một n b n .

Đây là mẫu đã hoàn thiện, với các trường hợp thử nghiệm bổ sung, bao gồm cả một trường hợp dài 10.000 ký tự:

$tests = array(
  'aaa', 'aaab', 'aaaxb', 'xaaab', 'b', 'abbb', 'aabb', 'aaabbbbb', 'aaaaabbb',
  '', 'ab', 'abb', 'aab', 'aaaabb', 'aaabbb', 'bbbaaa', 'ababab', 'abc',
  str_repeat('a', 5000).str_repeat('b', 5000)
);
 
$r6 = '/ ^ (?: a (?= a* (\1?+ b) ) )+ \1 $ /x';
#                     └──────┘  
#                         1     
#               └───────────────┘ 
#                   lookahead     
#          └───────────────────────┘
#             non-capturing group

Nó tìm thấy 4 trận đấu: ab, aabb, aaabbb, và một 5000 b 5000 . Chỉ mất 0,06 giây để chạy trên ideone.com .


Bước 7: Kiểm tra Java

Vì vậy, mẫu hoạt động trong PHP, nhưng mục đích cuối cùng là viết một mẫu hoạt động trong Java.

public static void main(String[] args) {
 
        String aNbN = "(?x) (?:  a  (?= a* (\\1?+ b))  )+ \\1";
        String[] tests = {
                "",      // false
                "ab",    // true
                "abb",   // false
                "aab",   // false
                "aabb",  // true
                "abab",  // false
                "abc",   // false
                repeat('a', 5000) + repeat('b', 4999), // false
                repeat('a', 5000) + repeat('b', 5000), // true
                repeat('a', 5000) + repeat('b', 5001), // false
        };
        for (String test : tests) {
                System.out.printf("[%s]%n  %s%n%n", test, test.matches(aNbN));
        }
 
}
 
static String repeat(char ch, int n) {
        return new String(new char[n]).replace('\0', ch);
}

Mô hình hoạt động như mong đợi ( như đã thấy trên ideone.com ).


Và bây giờ chúng ta đi đến kết luận ...

Cần phải nói rằng a*trong cái nhìn và thực sự là " +vòng lặp chính ", cả hai đều cho phép quay lui. Người đọc được khuyến khích xác nhận lý do tại sao đây không phải là một vấn đề về tính đúng đắn, và tại sao đồng thời khiến cho cả hai đều có hiệu quả (mặc dù có lẽ việc trộn định lượng sở hữu bắt buộc và không bắt buộc trong cùng một mẫu có thể dẫn đến nhận thức sai).

Cũng cần phải nói rằng mặc dù có một mẫu regex phù hợp với a n b n , nhưng đây không phải lúc nào cũng là giải pháp "tốt nhất" trong thực tế. Một giải pháp tốt hơn nhiều là chỉ cần đối sánh ^(a+)(b+)$và sau đó so sánh độ dài của các chuỗi được nhóm 1 và 2 nắm bắt trong ngôn ngữ lập trình lưu trữ.

Trong PHP, nó có thể trông giống như thế này ( như được thấy trong ideone.com ):

function is_anbn($s) {
   return (preg_match('/^(a+)(b+)$/', $s, $groups)) &&
      (strlen($groups[1]) == strlen($groups[2]));
}

Mục đích của bài viết này KHÔNG phải để thuyết phục người đọc rằng regex có thể làm được hầu hết mọi thứ; nó rõ ràng là không thể, và ngay cả đối với những thứ nó có thể làm, ít nhất ủy quyền một phần cho ngôn ngữ lưu trữ nên được xem xét nếu nó dẫn đến một giải pháp đơn giản hơn.

Như đã đề cập ở trên, mặc dù bài viết này nhất thiết phải được gắn thẻ [regex]cho stackoverflow, nhưng có lẽ nó còn nhiều hơn thế nữa. Mặc dù chắc chắn có giá trị trong việc tìm hiểu về các khẳng định, tham chiếu lồng nhau, định lượng sở hữu, v.v., nhưng có lẽ bài học lớn hơn ở đây là quá trình sáng tạo mà qua đó người ta có thể cố gắng giải quyết vấn đề, sự quyết tâm và chăm chỉ mà nó thường đòi hỏi khi bạn phải các ràng buộc khác nhau, thành phần có hệ thống từ các bộ phận khác nhau để xây dựng một giải pháp làm việc, v.v.


Tài liệu thưởng! Mẫu đệ quy PCRE!

Vì chúng tôi đã giới thiệu PHP, nên cần phải nói rằng PCRE hỗ trợ các chương trình con và mẫu đệ quy. Do đó, mẫu sau hoạt động cho preg_match( như đã thấy trên ideone.com ):

$rRecursive = '/ ^ (a (?1)? b) $ /x';

Hiện tại regex của Java không hỗ trợ mẫu đệ quy.


Tài liệu thưởng nhiều hơn nữa! Phù hợp với a n b n c n !!

Vì vậy, chúng ta đã thấy làm thế nào để so khớp a n b n không chính quy, nhưng vẫn không có ngữ cảnh, nhưng chúng ta cũng có thể so khớp a n b n c n , thậm chí không có ngữ cảnh không?

Tất nhiên câu trả lời là CÓ! Người đọc được khuyến khích cố gắng tự giải quyết vấn đề này, nhưng giải pháp được cung cấp bên dưới (với việc triển khai bằng Java trên ideone.com ).

^ (?: a (?= a* (\1?+ b) b* (\2?+ c) ) )+ \1 \2 $


Chắc chắn có thể có sai sót / lỗi chính tả trong câu trả lời dài này, vì vậy vui lòng để lại phản hồi dưới dạng nhận xét để tôi có thể tự sửa đổi chúng.
polygenelubricants

Bạn đã làm rất tốt. Tôi sẽ mất một lúc để đọc hết, nhưng dòng cuối cùng về cơ bản là không thể đọc được; nó là một phông chữ nhỏ như vậy. ------ Ồ, chờ đã. Đó có phải là một feature? .... Không chắc đó có phải là một ý kiến ​​hay không. Tôi biết ký hiệu cuối cùng là gì, nhưng nó không thể đọc được (ngoài việc sao chép dán nó).
Peter Ajtai

6
@Peter: Đánh dấu văn bản nhỏ và sao chép và dán vào thứ khác. Nó làm cho nó khó đọc với mục đích: đó là một spoiler, giải pháp cho câu đố bổ ích.
polygenelubricants

8
+1: Giải thích tuyệt vời, "Các bài báo nâng cao" này là những ý tưởng tuyệt vời.
Callum Rogers

1
@LarsH PHP's preg_match()là một ví dụ về PCRE . Các regex của Java dường như dựa trên phiên bản cũ hơn của Perl regexps . Điều đó có nghĩa là các regex trong PHP mạnh hơn phiên bản trong Java. Kể từ 2013-02-21 , pcre.txt nói rằng nó tương ứng với Perl 5,12 . Trong khi Perl hiện đang ở mức 5,16, với 5,18 một vài tháng nghỉ. (Có thực sự đã không được thêm nhiều điều để regexes trong thời gian đó)
Brad Gilbert

20

Do chưa có đề cập nào về việc PCRE hỗ trợ các mẫu đệ quy, tôi chỉ muốn nêu ra ví dụ đơn giản và hiệu quả nhất về PCRE mô tả ngôn ngữ được đề cập:

/^(a(?1)?b)$/

+1 wow, tôi không biết PCRE hỗ trợ mẫu đệ quy (Tôi vẫn đang học! Mỗi ngày!). Tôi đã sửa đổi bài viết để phù hợp với thông tin này. Tuy nhiên, tôi không nghĩ rằng mẫu đệ quy có thể phù hợp a^n b^n c^n.
polygenelubricants

Cần lưu ý rằng tùy chọn này đơn giản hơn, nhưng không tốt bằng câu trả lời đã đăng - đệ quy tràn trên các chuỗi dài.
Kobi

@Kobi Điều này phụ thuộc vào định nghĩa của bạn về "tốt". Ví dụ: giải pháp đệ quy nhanh hơn xung quanh một bậc lớn hơn bậc khác ( codepad.viper-7.com/CWgy7c ). Và nó còn dễ hiểu hơn nhiều. Giải pháp đệ quy gần như là chuyển đổi trực tiếp ngữ pháp thành một regex (thực ra bạn chỉ cần viết nó ở dạng ngữ pháp, nó sẽ hoạt động).
NikiC

1
@polygeniclubricants, bạn có thể so khớp mẫu đó với hai mẫu đệ quy, một mẫu sử dụng as và bs mà không thu thập (và xác minh có cùng số lượng w / đệ quy), tiếp theo là một regex thu thập tham gia tiêu thụ tất cả a, rồi áp dụng đệ quy để sử dụng và xác minh rằng có cùng số lượng bs và cs. Regex là: /^(?=(a(?-1)?b)c)a+(b(?-1)?c)$/x. Tín dụng cho: nikic.github.io/2012/06/15/…
Josh Reback

11

Như đã đề cập trong câu hỏi - với nhóm cân bằng .NET, các mẫu của loại a n b n c n d n … z n có thể được so khớp dễ dàng như

^
  (?<A>a)+
  (?<B-A>b)+  (?(A)(?!))
  (?<C-B>c)+  (?(B)(?!))
  ...
  (?<Z-Y>z)+  (?(Y)(?!))
$

Ví dụ: http://www.ideone.com/usuOE


Biên tập:

Ngoài ra còn có một mẫu PCRE cho ngôn ngữ tổng quát với mẫu đệ quy, nhưng cần có một cái nhìn trước. Tôi không nghĩ rằng đây là một bản dịch trực tiếp của phần trên.

^
  (?=(a(?-1)?b))  a+
  (?=(b(?-1)?c))  b+
  ...
  (?=(x(?-1)?y))  x+
     (y(?-1)?z)
$

Ví dụ: http://www.ideone.com/9gUwF


1
@poly: Cảm ơn :). Thực ra tôi không quen với các mẫu .NET, nhưng đối với loại mẫu này, nó hóa ra rất dễ dàng với các nhóm cân bằng, vì vậy tôi bổ sung câu trả lời này.
kennytm

bạn có thể làm điều này với mô hình đệ quy? Bởi vì nếu bạn không thể, đó là một bước ngoặt thú vị mà nhóm cân bằng có thể làm những điều mà mẫu đệ quy không thể. (Và vâng, tôi đánh giá rất cao phần bổ sung).
polygenelubricants

Nhân tiện, lý do tại sao tôi bỏ qua giải pháp .NET là vì tôi có kế hoạch cho "Làm thế nào chúng ta có thể kết hợp a^n b^nvới .NET regex?" trong tương lai, nhưng chúng tôi hoan nghênh bạn viết nó nếu bạn muốn. Tôi không làm những bài báo này chỉ cho bản thân mình; Tôi cũng muốn khuyến khích những người khác làm điều đó để có nội dung tốt trên trang web.
polygenelubricants,

Vui lòng cập nhật nếu bạn tìm ra cách thực hiện với các mẫu đệ quy. Tôi đã chơi với các nhóm cân bằng để nắm bắt các từ có độ dài tạo nên một chuỗi Fibonacci và không thể làm cho nó hoạt động. Nó có thể được thực hiện bằng cách sử dụng nhìn xung quanh, tương tự như những gì tôi đã làm.
Kobi

1
Tôi chỉ muốn chỉ ra rằng phiên bản PCRE của mẫu này hơi thiếu sót vì nó khớp nếu đoạn ký tự tiếp theo dài hơn đoạn trước. Xem ở đây: regex101.com/r/sdlRTm/1 Bạn cần phải thêm (?!b), (?!c)vv sau khi nhóm chụp như vậy: regex101.com/r/sdlRTm/2
jaytea
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.