Mẫu Regex để Đối sánh, Loại trừ khi… / Ngoại trừ giữa


108

- Chỉnh sửa-- Các câu trả lời hiện tại có một số ý tưởng hữu ích nhưng tôi muốn điều gì đó hoàn thiện hơn mà tôi có thể hiểu và sử dụng lại 100%; đó là lý do tại sao tôi đặt tiền thưởng. Ngoài ra, các ý tưởng hoạt động ở mọi nơi tốt hơn đối với tôi hơn là cú pháp không chuẩn\K

Câu hỏi này là về cách tôi có thể kết hợp một mẫu ngoại trừ một số tình huống s1 s2 s3. Tôi đưa ra một ví dụ cụ thể để thể hiện ý nghĩa của mình nhưng thích một câu trả lời chung chung mà tôi có thể hiểu 100% để tôi có thể sử dụng lại nó trong các tình huống khác.

Thí dụ

Tôi muốn so khớp năm chữ số bằng cách sử dụng \b\d{5}\bnhưng không phải trong ba trường hợp s1 s2 s3:

s1: Không phải ở dòng kết thúc bằng dấu chấm như câu này.

s2: Không ở bất kỳ đâu bên trong parens.

s3: Không nằm trong khối bắt đầu bằng if(và kết thúc bằng//endif

Tôi biết cách giải quyết bất kỳ lỗi nào trong s1 s2 s3 với lookahead và lookbehind, đặc biệt là trong C # lookbehind hoặc \Ktrong PHP.

Ví dụ

s1 (?m)(?!\d+.*?\.$)\d+

s3 với C # lookbehind (?<!if\(\D*(?=\d+.*?//endif))\b\d+\b

s3 với PHP \ K (?:(?:if\(.*?//endif)\D*)*\K\d+

Nhưng sự pha trộn của các điều kiện với nhau khiến đầu tôi như muốn nổ tung. Tin xấu hơn nữa là tôi có thể cần thêm các điều kiện khác s4 s5 vào lúc khác.

Tin tốt là tôi không quan tâm nếu tôi xử lý các tệp bằng hầu hết các ngôn ngữ phổ biến như PHP, C #, Python hay máy giặt của hàng xóm. :) Tôi khá là người mới bắt đầu về Python & Java nhưng muốn tìm hiểu xem nó có giải pháp không.

Vì vậy, tôi đến đây để xem liệu ai đó nghĩ ra một công thức linh hoạt.

Gợi ý là ổn: bạn không cần phải cung cấp cho tôi mã đầy đủ. :)

Cảm ơn bạn.


1
\Kkhông phải là cú pháp php đặc biệt. Hãy giải thích và làm rõ những gì bạn muốn nói. Nếu bạn muốn nói với chúng tôi rằng bạn không cần một giải pháp "phức tạp", bạn phải nói điều gì phức tạp đối với bạn và tại sao.
hakre

@hakre Ý bạn là vì ruby ​​hiện đang sử dụng nó và nó bắt đầu bằng perl?
Hans Schindler

1
Không, vì đó là PCRE không phải PHP (cũng không phải Ruby). Perl thì khác, tuy nhiên PCRE hướng tới tương thích với Perl Regex.
hakre

Yêu cầu s2 và s3 của bạn có vẻ trái ngược nhau. s2 ngụ ý rằng các dấu ngoặc đơn luôn luôn khớp và có thể được lồng vào nhau, nhưng s3 yêu cầu "if("đóng dấu ngoặc kép: mở, không phải với a ")", mà là với dấu "//endif":? Và nếu đối với s3, bạn thực sự có ý rằng mệnh đề if nên được đóng bằng "//endif)":, thì yêu cầu s3 là một tập con của s2.
ridgerunner

@hakre Vâng, tôi biết PCRE nhưng để giải thích, câu hỏi là về ngôn ngữ lập trình ... nó nói especially in C# lookbehind or \K in PHP... Nhưng C # nhìn sau không chỉ C # mà là .NET nên bạn cũng có thể phàn nàn, tôi nói C # không phải .NET :) Và để trả lời, tôi nói Ruby không phải Onigurama cũng tệ ... Có ngôn ngữ nào khác sử dụng PCRE không? Không nói về Notepad ++ hoặc các công cụ máy chủ này là câu hỏi về việc sử dụng tính năng trong ngôn ngữ tôi hy vọng những giải thích và xin lỗi nếu nó trông giống sai
Hans Schindler

Câu trả lời:


205

Hans, tôi sẽ cắn câu và xác định câu trả lời trước đó của mình. Bạn nói rằng bạn muốn "một cái gì đó hoàn chỉnh hơn", vì vậy tôi hy vọng bạn sẽ không bận tâm đến câu trả lời dài dòng — chỉ đang cố gắng làm hài lòng. Hãy bắt đầu với một số nền tảng.

Trước hết, đây là một câu hỏi tuyệt vời. Thường có những câu hỏi về việc đối sánh các mẫu nhất định ngoại trừ trong một số ngữ cảnh nhất định (ví dụ: trong một khối mã hoặc bên trong dấu ngoặc đơn). Những câu hỏi này thường dẫn đến những giải pháp khá khó xử. Vì vậy, câu hỏi của bạn về nhiều ngữ cảnh là một thách thức đặc biệt.

Sự ngạc nhiên

Đáng ngạc nhiên là có ít nhất một giải pháp hiệu quả chung, dễ thực hiện và dễ duy trì. Nó hoạt động với tất cả các hương vị regex cho phép bạn kiểm tra các nhóm nắm bắt trong mã của mình. Và nó tình cờ trả lời một số câu hỏi phổ biến thoạt nghe có vẻ khác với câu hỏi của bạn: "khớp với mọi thứ ngoại trừ Bánh rán", "thay thế tất cả trừ ...", "khớp với tất cả các từ ngoại trừ những từ trong danh sách đen của mẹ tôi", "bỏ qua các thẻ "," khớp nhiệt độ trừ khi được in nghiêng "...

Đáng buồn thay, kỹ thuật này không được biết đến nhiều: Tôi ước tính rằng trong 20 câu hỏi SO có thể sử dụng nó, chỉ có một câu trả lời có một câu trả lời đề cập đến nó — có nghĩa là có thể một trong năm mươi hoặc sáu mươi câu trả lời. Xem cuộc trao đổi của tôi với Kobi trong phần bình luận. Kỹ thuật này được mô tả một cách chuyên sâu trong bài viết này và gọi nó (một cách lạc quan) là "thủ thuật regex tốt nhất từ ​​trước đến nay". Không đi sâu vào chi tiết, tôi sẽ cố gắng cung cấp cho bạn một cách chắc chắn về cách thức hoạt động của kỹ thuật này. Để biết thêm chi tiết và các mẫu mã bằng nhiều ngôn ngữ khác nhau, tôi khuyến khích bạn tham khảo tài nguyên đó.

Một biến thể được biết đến nhiều hơn

Có một biến thể sử dụng cú pháp cụ thể cho Perl và PHP thực hiện tương tự. Bạn sẽ thấy nó trên SO dưới bàn tay của các bậc thầy regex như CasimiretHippolyteHamZa . Tôi sẽ cho bạn biết thêm về điều này bên dưới, nhưng trọng tâm của tôi ở đây là giải pháp chung hoạt động với tất cả các hương vị regex (miễn là bạn có thể kiểm tra các nhóm nắm bắt trong mã của mình).

Cảm ơn vì tất cả nền, zx81 ... Nhưng công thức là gì?

Thực tế then chốt

Phương thức này trả về kết quả phù hợp trong chụp Nhóm 1. Nó không quan tâm chút nào đến trận đấu tổng thể.

Trên thực tế, mẹo là để khớp các ngữ cảnh khác nhau mà chúng ta không muốn (xâu chuỗi các ngữ cảnh này bằng cách sử dụng |OR / luân phiên) để "vô hiệu hóa chúng". Sau khi phù hợp với tất cả các tình huống không mong muốn, phần cuối cùng của luân phiên phù hợp với những gì chúng ta làm muốn và chụp nó vào nhóm 1.

Công thức chung là

Not_this_context|Not_this_either|StayAway|(WhatYouWant)

Điều này sẽ khớp Not_this_context, nhưng theo nghĩa nào đó, trận đấu đó sẽ trở thành thùng rác, bởi vì chúng tôi sẽ không xem xét các trận đấu tổng thể: chúng tôi chỉ xem xét ảnh chụp của Nhóm 1.

Trong trường hợp của bạn, với các chữ số và ba ngữ cảnh cần bỏ qua, chúng tôi có thể làm:

s1|s2|s3|(\b\d+\b)

Lưu ý rằng vì chúng tôi thực sự đối sánh s1, s2 và s3 thay vì cố gắng tránh chúng bằng cách nhìn xung quanh, các biểu thức riêng lẻ cho s1, s2 và s3 có thể vẫn rõ ràng như ngày. (Chúng là biểu thức con ở mỗi bên của a |)

Toàn bộ biểu thức có thể được viết như thế này:

(?m)^.*\.$|\([^\)]*\)|if\(.*?//endif|(\b\d+\b)

Xem bản trình diễn này (nhưng tập trung vào các nhóm chụp ở ngăn dưới bên phải.)

Nếu bạn cố gắng chia nhỏ regex này ở mỗi |dấu phân cách, nó thực sự chỉ là một chuỗi bốn biểu thức rất đơn giản.

Đối với các hương vị hỗ trợ khoảng cách trống, điều này đặc biệt tốt.

(?mx)
      ### s1: Match line that ends with a period ###
^.*\.$  
|     ### OR s2: Match anything between parentheses ###
\([^\)]*\)  
|     ### OR s3: Match any if(...//endif block ###
if\(.*?//endif  
|     ### OR capture digits to Group 1 ###
(\b\d+\b)

Điều này đặc biệt dễ đọc và dễ bảo trì.

Mở rộng regex

Khi bạn muốn bỏ qua các tình huống khác s4 và s5, bạn thêm chúng vào các thay thế khác ở bên trái:

s4|s5|s1|s2|s3|(\b\d+\b)

Cái này hoạt động ra sao?

Các ngữ cảnh bạn không muốn sẽ được thêm vào danh sách thay thế ở bên trái: chúng sẽ khớp với nhau, nhưng những kết quả trùng khớp tổng thể này không bao giờ được kiểm tra, vì vậy việc so khớp chúng là một cách để đưa chúng vào "thùng rác".

Tuy nhiên, nội dung bạn muốn sẽ được chuyển vào Nhóm 1. Sau đó, bạn phải kiểm tra theo chương trình xem Nhóm 1 có được đặt và không trống hay không. Đây là một nhiệm vụ lập trình tầm thường (và sau này chúng ta sẽ nói về cách nó được thực hiện), đặc biệt là khi nó để lại cho bạn một regex đơn giản mà bạn có thể hiểu trong nháy mắt và sửa đổi hoặc mở rộng theo yêu cầu.

Tôi không phải lúc nào cũng thích hình dung, nhưng cái này làm rất tốt việc chỉ ra phương pháp đơn giản như thế nào. Mỗi "dòng" tương ứng với một trận đấu tiềm năng, nhưng chỉ dòng cuối cùng được đưa vào Nhóm 1.

Hình ảnh hóa biểu thức chính quy

Bản trình diễn gỡ lỗi

Biến thể Perl / PCRE

Trái ngược với giải pháp chung ở trên, tồn tại một biến thể cho Perl và PCRE thường thấy trên SO, ít nhất là trong tay của các Thần regex như @CasimiretHippolyte và @HamZa. Nó là:

(?:s1|s2|s3)(*SKIP)(*F)|whatYouWant

Trong trường hợp của bạn:

(?m)(?:^.*\.$|\([^()]*\)|if\(.*?//endif)(*SKIP)(*F)|\b\d+\b

Biến thể này dễ sử dụng hơn một chút vì nội dung phù hợp trong ngữ cảnh s1, s2 và s3 chỉ đơn giản là bỏ qua, vì vậy bạn không cần phải kiểm tra các ảnh chụp Nhóm 1 (lưu ý rằng dấu ngoặc đơn đã biến mất). Các trận đấu chỉ chứawhatYouWant

Lưu ý rằng (*F), (*FAIL)(?!)tất cả đều giống nhau. Nếu bạn muốn mờ mịt hơn, bạn có thể sử dụng(*SKIP)(?!)

demo cho phiên bản này

Các ứng dụng

Dưới đây là một số vấn đề phổ biến mà kỹ thuật này thường có thể dễ dàng giải quyết. Bạn sẽ nhận thấy rằng lựa chọn từ có thể làm cho một số vấn đề này nghe có vẻ khác biệt trong khi trên thực tế, chúng hầu như giống hệt nhau.

  1. Làm cách nào để đối sánh foo ngoại trừ bất kỳ vị trí nào trong thẻ như thế <a stuff...>...</a>nào?
  2. Làm cách nào để đối sánh foo ngoại trừ trong <i>thẻ hoặc đoạn mã javascript (thêm điều kiện)?
  3. Làm cách nào để ghép tất cả các từ không có trong danh sách đen này?
  4. Làm thế nào tôi có thể bỏ qua bất cứ điều gì bên trong một khối SUB ... KẾT THÚC SUB?
  5. Làm cách nào để ghép mọi thứ ngoại trừ ... s1 s2 s3?

Cách lập trình Chụp nhóm 1

Bạn không phải đối với mã, nhưng để hoàn thành ... Mã để kiểm tra Nhóm 1 rõ ràng sẽ phụ thuộc vào ngôn ngữ bạn chọn. Ở bất kỳ mức độ nào, nó không nên thêm nhiều hơn một vài dòng vào mã bạn sẽ sử dụng để kiểm tra các kết quả phù hợp.

Nếu nghi ngờ, tôi khuyên bạn nên xem phần mẫu mã của bài viết đã đề cập trước đó, phần này trình bày mã cho khá nhiều ngôn ngữ.

Giải pháp thay thế

Tùy thuộc vào mức độ phức tạp của câu hỏi và công cụ regex được sử dụng, có một số lựa chọn thay thế. Đây là hai điều kiện có thể áp dụng cho hầu hết các trường hợp, bao gồm nhiều điều kiện. Theo quan điểm của tôi, s1|s2|s3|(whatYouWant)công thức này gần như không hấp dẫn bằng , nếu chỉ vì sự rõ ràng luôn chiến thắng.

1. Replace then Match.

Một giải pháp tốt nghe có vẻ khó hiểu nhưng hoạt động tốt trong nhiều môi trường là thực hiện theo hai bước. Một regex đầu tiên vô hiệu hóa ngữ cảnh bạn muốn bỏ qua bằng cách thay thế các chuỗi có thể xung đột. Nếu bạn chỉ muốn đối sánh, thì bạn có thể thay thế bằng một chuỗi trống, sau đó chạy đối sánh của bạn ở bước thứ hai. Nếu bạn muốn thay thế, trước tiên bạn có thể thay thế các chuỗi bị bỏ qua bằng một thứ gì đó khác biệt, chẳng hạn bao quanh các chữ số của bạn bằng một chuỗi có chiều rộng cố định là @@@. Sau lần thay thế này, bạn có thể tự do thay thế những gì bạn thực sự muốn, sau đó bạn sẽ phải hoàn nguyên các @@@chuỗi đặc biệt của mình .

2. Cách nhìn.

Bài đăng ban đầu của bạn cho thấy rằng bạn hiểu cách loại trừ một điều kiện duy nhất bằng cách sử dụng cách xem xét. Bạn đã nói rằng C # là tuyệt vời cho điều này, và bạn đã đúng, nhưng nó không phải là lựa chọn duy nhất. Ví dụ, các phiên bản .NET regex được tìm thấy trong C #, VB.NET và Visual C ++, cũng như regexmô-đun vẫn đang thử nghiệm để thay thế rebằng Python, là hai công cụ duy nhất mà tôi biết hỗ trợ lookbehind chiều rộng vô hạn. Với những công cụ này, một điều kiện trong một cái nhìn sau có thể giúp bạn không chỉ nhìn về phía sau mà còn cả trận đấu và xa hơn trận đấu, tránh phải phối hợp với một cái nhìn trước. Thêm điều kiện? Nhiều cách nhìn hơn.

Tái chế regex bạn có cho s3 trong C #, toàn bộ mẫu sẽ giống như thế này.

(?!.*\.)(?<!\([^()]*(?=\d+[^)]*\)))(?<!if\(\D*(?=\d+.*?//endif))\b\d+\b

Nhưng bây giờ bạn biết tôi không đề xuất điều này, phải không?

Xóa

@HamZa và @Jerry đã đề xuất tôi đề cập đến một thủ thuật bổ sung cho các trường hợp khi bạn tìm cách xóa WhatYouWant. Bạn nhớ rằng công thức để phù hợp WhatYouWant(nắm bắt nó vào Nhóm 1) s1|s2|s3|(WhatYouWant), phải không? Để xóa tất cả các phiên bản của WhatYouWant, bạn thay đổi regex thành

(s1|s2|s3)|WhatYouWant

Đối với chuỗi thay thế, bạn sử dụng $1. Điều xảy ra ở đây là đối với mỗi phiên bản s1|s2|s3được so khớp, phần thay $1thế sẽ thay thế phiên bản đó bằng chính nó (được tham chiếu bởi $1). Mặt khác, khi WhatYouWantđược so khớp, nó được thay thế bằng một nhóm trống và không có gì khác - và do đó bị xóa. Hãy xem bản demo này , cảm ơn bạn @HamZa và @Jerry đã đề xuất bổ sung tuyệt vời này.

Thay thế

Điều này đưa chúng ta đến các thiết bị thay thế mà tôi sẽ đề cập ngắn gọn.

  1. Khi thay thế bằng không, hãy xem thủ thuật "Xóa" ở trên.
  2. Khi thay thế, nếu sử dụng Perl hoặc PCRE, hãy sử dụng (*SKIP)(*F)biến thể được đề cập ở trên để khớp chính xác những gì bạn muốn và thực hiện thay thế ngay lập tức.
  3. Trong các phiên bản khác, trong lệnh gọi hàm thay thế, hãy kiểm tra khớp bằng cách sử dụng lệnh gọi lại hoặc lambda và thay thế nếu Nhóm 1 được đặt. Nếu bạn cần trợ giúp về vấn đề này, bài viết đã được tham khảo sẽ cung cấp cho bạn mã bằng nhiều ngôn ngữ khác nhau.

Chúc vui vẻ!

Không, chờ đã, còn nhiều hơn nữa!

À, không, tôi sẽ để dành số đó cho hồi ký của mình thành hai mươi tập, sẽ được phát hành vào mùa xuân năm sau.


2
@Kobi Trả lời hai phần. Vâng, tôi đã mang đi viết đêm qua và viết ở dưới cùng rằng tôi sẽ ngủ trên đó và dọn dẹp sau. :) Vâng, mẹo rất đơn giản nhưng tôi không chia sẻ nhận thức của bạn rằng nó là "cơ bản" vì nó dường như không phải là một phần của các công cụ phổ biến mà mọi người sử dụng để giải quyết các vấn đề loại trừ. Khi tôi tìm kiếm các vấn đề "ngoại trừ" hoặc "trừ khi" hoặc "không bên trong" trên Google, chỉ có một câu trả lời (không có phiếu bầu) đề xuất nó, không câu trả lời nào trong số những người khác đã làm. Nhân tiện, tôi chưa thấy câu trả lời của bạn, thật tuyệt vời. :)
zx81

2
Xin lỗi, nhưng "Thủ thuật tốt nhất" của Rex chỉ đơn giản là không hoạt động ( đáng tin cậy ). Giả sử bạn muốn đối sánh Tarzan, nhưng không phải khi ở bất kỳ đâu bên trong dấu ngoặc kép. : /no|no|(yes)/Trick regex sẽ giống như: /"[^"]*"|Tarzan/(bỏ qua các ký tự đã thoát). Điều này sẽ làm việc cho nhiều trường hợp, nhưng không hoàn toàn khi áp dụng cho các văn bản JavaScript hợp lệ sau đây: var bug1 = 'One " quote here. Should match this Tarzan'; var bug2 = "Should not match this Tarzan";. Thủ thuật của Rex chỉ hoạt động khi TẤT CẢ các cấu trúc có thể được khớp - nói cách khác - bạn cần phải phân tích cú pháp hoàn toàn văn bản để đảm bảo độ chính xác 100%.
ridgerunner

1
Xin lỗi nếu tôi nghe có vẻ gay gắt - đó chắc chắn không phải là ý định của tôi. Quan điểm của tôi (như trong nhận xét thứ hai của tôi cho câu hỏi ban đầu ở trên) là một giải pháp đúng phụ thuộc nhiều vào văn bản đích đang được tìm kiếm. Ví dụ của tôi có mã nguồn JavaScript là văn bản đích có một dấu ngoặc kép kèm theo trong một chuỗi được trích dẫn đơn. Nó có thể dễ dàng giống như một RegExp theo nghĩa đen chẳng hạn như: var bug1 = /"[^"]*"|(Tarzan)/gi;và có tác dụng tương tự (và ví dụ thứ hai này chắc chắn không phải là một trường hợp cạnh). Có nhiều ví dụ khác mà tôi có thể trích dẫn mà kỹ thuật này không hoạt động đáng tin cậy.
ridgerunner

1
@ridgerunner Tôi luôn thích nghe từ bạn, điều đó nghe có vẻ khắc nghiệt một cách vô cớ đối với tôi. Khi chúng tôi biết rằng chuỗi của chúng tôi có thể chứa "cảnh báo sai", chúng tôi đều điều chỉnh các mẫu của mình. Ví dụ: để so khớp một chuỗi có thể chứa dấu ngoặc kép có thể làm hỏng trình khớp chuỗi, bạn có thể sử dụng (?<!\\)"(?:\\"|[^"\r\n])*+" Bạn không kéo các khẩu súng lớn trừ khi bạn có lý do. Nguyên tắc của giải pháp vẫn còn giá trị. Nếu chúng ta không thể thể hiện một hình mẫu để đặt ở phía bên trái, đó là một câu chuyện khác, chúng ta cần một giải pháp khác. Nhưng giải pháp thực hiện những gì nó quảng cáo.
zx81

1
Câu trả lời này đã được người dùng @funkwurm thêm vào Câu hỏi thường gặp về Cụm từ Thông dụng Tràn chồng.
aliteralmind

11

Thực hiện ba trận đấu khác nhau và xử lý sự kết hợp của ba tình huống bằng cách sử dụng logic có điều kiện trong chương trình. Bạn không cần phải xử lý mọi thứ trong một regex khổng lồ.

CHỈNH SỬA: hãy để tôi mở rộng một chút vì câu hỏi trở nên thú vị hơn :-)

Ý tưởng chung mà bạn đang cố gắng nắm bắt ở đây là khớp với một mẫu regex nhất định, nhưng không phải khi có một số mẫu nhất định (có thể là bất kỳ số nào) khác xuất hiện trong chuỗi thử nghiệm. May mắn thay, bạn có thể tận dụng lợi thế của ngôn ngữ lập trình của mình: giữ cho regexes đơn giản và chỉ sử dụng một điều kiện phức hợp. Cách tốt nhất là nắm bắt ý tưởng này trong một thành phần có thể tái sử dụng, vì vậy hãy tạo một lớp và một phương thức triển khai nó:

using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;

public class MatcherWithExceptions {
  private string m_searchStr;
  private Regex m_searchRegex;
  private IEnumerable<Regex> m_exceptionRegexes;

  public string SearchString {
    get { return m_searchStr; }
    set {
      m_searchStr = value;
      m_searchRegex = new Regex(value);
    }
  }

  public string[] ExceptionStrings {
    set { m_exceptionRegexes = from es in value select new Regex(es); }
  }

  public bool IsMatch(string testStr) {
    return (
      m_searchRegex.IsMatch(testStr)
      && !m_exceptionRegexes.Any(er => er.IsMatch(testStr))
    );
  }
}

public class App {
  public static void Main() {
    var mwe = new MatcherWithExceptions();

    // Set up the matcher object.
    mwe.SearchString = @"\b\d{5}\b";
    mwe.ExceptionStrings = new string[] {
      @"\.$"
    , @"\(.*" + mwe.SearchString + @".*\)"
    , @"if\(.*" + mwe.SearchString + @".*//endif"
    };

    var testStrs = new string[] {
      "1." // False
    , "11111." // False
    , "(11111)" // False
    , "if(11111//endif" // False
    , "if(11111" // True
    , "11111" // True
    };

    // Perform the tests.
    foreach (var ts in testStrs) {
      System.Console.WriteLine(mwe.IsMatch(ts));
    }
  }
}

Vì vậy, ở trên, chúng tôi thiết lập chuỗi tìm kiếm (năm chữ số), nhiều chuỗi ngoại lệ ( s1 , s2s3 của bạn ), sau đó thử đối sánh với một số chuỗi kiểm tra. Kết quả được in ra phải được hiển thị trong các chú thích bên cạnh mỗi chuỗi thử nghiệm.


2
Ý bạn là có thể kết hợp ba regex liên tiếp? Regex 1 loại bỏ tình huống 1 (có thể chỉ xóa chữ số xấu), r2 loại bỏ s2, r3 loại bỏ s3 và khớp các chữ số còn lại? Đó là một ý tưởng thú vị.
Hans Schindler

Ha, chắc chắn, đó là lý do tại sao tôi ủng hộ bạn. :) Đừng hiểu sai ý tôi, tôi vẫn nghĩ rằng trong trường hợp cụ thể này, câu trả lời của tôi hiệu quả hơn và có thể bảo trì được. Bạn đã thấy phiên bản khoảng cách tự do mà tôi đã thêm ngày hôm qua chưa? Đó là một lần và đặc biệt dễ đọc và dễ bảo trì. Nhưng tôi thích công việc của bạn và câu trả lời mở rộng của bạn. Xin lỗi, tôi không thể ủng hộ lại, nếu không thì tôi sẽ làm. :)
zx81

2

Yêu cầu của bạn rằng nó không bên trong parens là không thể đáp ứng cho mọi trường hợp. Cụ thể, nếu bạn bằng cách nào đó có thể tìm thấy một (bên trái và )bên phải, điều đó không có nghĩa là bạn luôn ở bên trong. Ví dụ.

(....) + 55555 + (.....)- bên trong parens chưa có ()bên trái và bên phải

Bây giờ bạn có thể nghĩ rằng mình thông minh và chỉ tìm kiếm (bên trái nếu bạn không gặp phải )trước đó và ngược lại với bên phải. Điều này sẽ không hoạt động cho trường hợp này:

((.....) + 55555 + (.....))- bên trong parens mặc dù có đóng cửa )(sang trái và sang phải.

Không thể tìm hiểu xem bạn có đang ở bên trong parens bằng regex hay không, vì regex không thể đếm bao nhiêu parens đã được mở và bao nhiêu đã đóng.

Hãy xem xét nhiệm vụ dễ dàng hơn này: sử dụng regex, tìm hiểu xem tất cả các parens (có thể lồng nhau) trong một chuỗi đã đóng hay chưa, đó là cho mọi thứ (bạn cần tìm ). Bạn sẽ thấy rằng nó không thể giải được và nếu bạn không thể giải quyết điều đó với regex thì bạn không thể tìm ra liệu một từ có bên trong parens cho tất cả các trường hợp hay không, vì bạn không thể tìm ra ở một vị trí nào đó trong chuỗi nếu tất cả trước đó (có một tương ứng ).


2
Không ai nói gì về dấu ngoặc đơn lồng nhau và trường hợp số 1 của bạn được xử lý tốt bởi câu trả lời của zx81.
Dan Bechard

Cảm ơn bạn đã suy nghĩ thoải mái :) nhưng lồng ngoặc không làm tôi lo lắng cho câu hỏi này nó thêm về ý tưởng về những tình huống xấu s1 s2 s3
Hans Schindler

Tất nhiên nó không phải là không thể! Đây chính là lý do tại sao bạn cần theo dõi mức parens mà bạn hiện đang phân tích cú pháp.
MrWonderful

Chà nếu bạn đang phân tích một số loại CFG như OP dường như đang làm, bạn sẽ được phục vụ tốt hơn bằng cách tạo LALR hoặc trình phân tích cú pháp tương tự mà không gặp vấn đề với điều này.
RokL

2

Hans nếu bạn không phiền, tôi đã sử dụng máy giặt của hàng xóm của bạn có tên là perl :)

Đã chỉnh sửa: Bên dưới mã giả:

  loop through input
  if line contains 'if(' set skip=true
        if skip= true do nothing
        else
           if line match '\b\d{5}\b' set s0=true
           if line does not match s1 condition  set s1=true
           if line does not match s2 condition  set s2=true
           if s0,s1,s2 are true print line 
  if line contains '//endif' set skip=false

Đưa ra tệp input.txt:

tiago@dell:~$ cat input.txt 
this is a text
it should match 12345
if(
it should not match 12345
//endif 
it should match 12345
it should not match 12345.
it should not match ( blabla 12345  blablabla )
it should not match ( 12345 )
it should match 12345

Và trình xác thực tập lệnh.pl:

tiago@dell:~$ cat validator.pl 
#! /usr/bin/perl
use warnings;
use strict;
use Data::Dumper;

sub validate_s0 {
    my $line = $_[0];
    if ( $line =~ \d{5/ ){
        return "true";
    }
    return "false";
}

sub validate_s1 {
    my $line = $_[0];
    if ( $line =~ /\.$/ ){
        return "false";
    }
    return "true";
}

sub validate_s2 {
    my $line = $_[0];
    if ( $line =~ /.*?\(.*\d{5.*?\).*/ ){
        return "false";
    }
    return "true";
}

my $skip = "false";
while (<>){
    my $line = $_; 

    if( $line =~ /if\(/ ){
       $skip = "true";  
    }

    if ( $skip eq "false" ) {
        my $s0_status = validate_s0 "$line"; 
        my $s1_status = validate_s1 "$line";
        my $s2_status = validate_s2 "$line";

        if ( $s0_status eq "true"){
            if ( $s1_status eq "true"){
                if ( $s2_status eq "true"){
                    print "$line";
                }
            }
        }
    } 

    if ( $line =~ /\/\/endif/) {
        $skip="false";
    }
}

Chấp hành:

tiago @ dell: ~ $ cat input.txt | perl validator.pl
nó phải khớp với 12345
nó phải khớp với 12345
nó phải khớp với 12345

2

Không chắc liệu điều này có giúp ích cho bạn hay không, nhưng tôi đang cung cấp giải pháp xem xét các giả định sau:

  1. Bạn cần một giải pháp thanh lịch để kiểm tra tất cả các điều kiện
  2. Các điều kiện có thể thay đổi trong tương lai và bất cứ lúc nào.
  3. Một điều kiện không nên phụ thuộc vào người khác.

Tuy nhiên, tôi cũng đã xem xét những điều sau -

  1. Tệp được cung cấp có ít lỗi trong đó. Nếu nó xảy ra thì mã của tôi có thể cần một số sửa đổi để đối phó với điều đó.
  2. Tôi đã sử dụng Stack để theo dõi các if(khối.

Ok đây là giải pháp -

Tôi đã sử dụng C # và với nó là MEF (Microsoft Extensibility Framework) để triển khai các trình phân tích cú pháp có thể định cấu hình. Ý tưởng là, sử dụng một trình phân tích cú pháp duy nhất để phân tích cú pháp và danh sách các lớp trình xác thực có thể định cấu hình để xác thực dòng và trả về true hoặc false dựa trên việc xác nhận. Sau đó, bạn có thể thêm hoặc xóa bất kỳ trình xác thực nào bất kỳ lúc nào hoặc thêm trình xác thực mới nếu bạn muốn. Cho đến nay tôi đã triển khai cho S1, S2 và S3 mà bạn đã đề cập, hãy kiểm tra các lớp ở điểm 3. Bạn phải thêm các lớp cho s4, s5 nếu bạn cần trong tương lai.

  1. Đầu tiên, Tạo các Giao diện -

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace FileParserDemo.Contracts
    {
        public interface IParser
        {
            String[] GetMatchedLines(String filename);
        }
    
        public interface IPatternMatcher
        {
            Boolean IsMatched(String line, Stack<string> stack);
        }
    }
  2. Sau đó đến trình đọc và kiểm tra tệp -

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using FileParserDemo.Contracts;
    using System.ComponentModel.Composition.Hosting;
    using System.ComponentModel.Composition;
    using System.IO;
    using System.Collections;
    
    namespace FileParserDemo.Parsers
    {
        public class Parser : IParser
        {
            [ImportMany]
            IEnumerable<Lazy<IPatternMatcher>> parsers;
            private CompositionContainer _container;
    
            public void ComposeParts()
            {
                var catalog = new AggregateCatalog();
                catalog.Catalogs.Add(new AssemblyCatalog(typeof(IParser).Assembly));
                _container = new CompositionContainer(catalog);
                try
                {
                    this._container.ComposeParts(this);
                }
                catch
                {
    
                }
            }
    
            public String[] GetMatchedLines(String filename)
            {
                var matched = new List<String>();
                var stack = new Stack<string>();
                using (StreamReader sr = File.OpenText(filename))
                {
                    String line = "";
                    while (!sr.EndOfStream)
                    {
                        line = sr.ReadLine();
                        var m = true;
                        foreach(var matcher in this.parsers){
                            m = m && matcher.Value.IsMatched(line, stack);
                        }
                        if (m)
                        {
                            matched.Add(line);
                        }
                     }
                }
                return matched.ToArray();
            }
        }
    }
  3. Sau đó đến việc thực hiện các kiểm tra cá nhân, tên lớp tự giải thích, vì vậy tôi không nghĩ rằng chúng cần mô tả thêm.

    using FileParserDemo.Contracts;
    using System;
    using System.Collections.Generic;
    using System.ComponentModel.Composition;
    using System.Linq;
    using System.Text;
    using System.Text.RegularExpressions;
    using System.Threading.Tasks;
    
    namespace FileParserDemo.PatternMatchers
    {
        [Export(typeof(IPatternMatcher))]
        public class MatchAllNumbers : IPatternMatcher
        {
            public Boolean IsMatched(String line, Stack<string> stack)
            {
                var regex = new Regex("\\d+");
                return regex.IsMatch(line);
            }
        }
    
        [Export(typeof(IPatternMatcher))]
        public class RemoveIfBlock : IPatternMatcher
        {
            public Boolean IsMatched(String line, Stack<string> stack)
            {
                var regex = new Regex("if\\(");
                if (regex.IsMatch(line))
                {
                    foreach (var m in regex.Matches(line))
                    {
                        //push the if
                        stack.Push(m.ToString());
                    }
                    //ignore current line, and will validate on next line with stack
                    return true;
                }
                regex = new Regex("//endif");
                if (regex.IsMatch(line))
                {
                    foreach (var m in regex.Matches(line))
                    {
                        stack.Pop();
                    }
                }
                return stack.Count == 0; //if stack has an item then ignoring this block
            }
        }
    
        [Export(typeof(IPatternMatcher))]
        public class RemoveWithEndPeriod : IPatternMatcher
        {
            public Boolean IsMatched(String line, Stack<string> stack)
            {
                var regex = new Regex("(?m)(?!\\d+.*?\\.$)\\d+");
                return regex.IsMatch(line);
            }
        }
    
    
        [Export(typeof(IPatternMatcher))]
        public class RemoveWithInParenthesis : IPatternMatcher
        {
            public Boolean IsMatched(String line, Stack<string> stack)
            {
                var regex = new Regex("\\(.*\\d+.*\\)");
                return !regex.IsMatch(line);
            }
        }
    }
  4. Chương trình -

    using FileParserDemo.Contracts;
    using FileParserDemo.Parsers;
    using System;
    using System.Collections.Generic;
    using System.ComponentModel.Composition;
    using System.IO;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace FileParserDemo
    {
        class Program
        {
            static void Main(string[] args)
            {
                var parser = new Parser();
                parser.ComposeParts();
                var matches = parser.GetMatchedLines(Path.GetFullPath("test.txt"));
                foreach (var s in matches)
                {
                    Console.WriteLine(s);
                }
                Console.ReadLine();
            }
        }
    }

Để thử nghiệm, tôi lấy tệp mẫu của @ Tiago Test.txtcó các dòng sau:

this is a text
it should match 12345
if(
it should not match 12345
//endif 
it should match 12345
it should not match 12345.
it should not match ( blabla 12345  blablabla )
it should not match ( 12345 )
it should match 12345

Cung cấp đầu ra -

it should match 12345
it should match 12345
it should match 12345

Không biết điều này có giúp ích cho bạn hay không, tôi đã có một thời gian vui vẻ khi chơi với nó .... :)

Phần tốt nhất với nó là, để thêm một điều kiện mới, tất cả những gì bạn phải làm là cung cấp việc triển khai IPatternMatcher, nó sẽ tự động được gọi và do đó sẽ xác thực.


2

Tương tự như @ zx81 (*SKIP)(*F)nhưng sử dụng xác nhận tiêu cực trước.

(?m)(?:if\(.*?\/\/endif|\([^()]*\))(*SKIP)(*F)|\b\d+\b(?!.*\.$)

BẢN GIỚI THIỆU

Trong python, tôi sẽ dễ dàng làm như thế này,

import re
string = """cat 123 sat.
I like 000 not (456) though 111 is fine
222 if(  //endif if(cat==789 stuff  //endif   333"""
for line in string.split('\n'):                                  # Split the input according to the `\n` character and then iterate over the parts.
    if not line.endswith('.'):                                   # Don't consider the part which ends with a dot.
        for i in re.split(r'\([^()]*\)|if\(.*?//endif', line):   # Again split the part by brackets or if condition which endswith `//endif` and then iterate over the inner parts.
            for j in re.findall(r'\b\d+\b', i):                  # Then find all the numbers which are present inside the inner parts and then loop through the fetched numbers.
                print(j)                                         # Prints the number one ny one.

Đầu ra:

000
111
222
333
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.