ฮันส์ฉันจะเอาเหยื่อและเนื้อออกมาจากคำตอบก่อนหน้านี้ คุณบอกว่าคุณต้องการ "บางสิ่งบางอย่างที่สมบูรณ์ยิ่งขึ้น" ดังนั้นฉันหวังว่าคุณจะไม่สนใจคำตอบที่ยาวเพียงแค่พยายามทำให้พอใจ เริ่มต้นด้วยพื้นหลังบางส่วน
ก่อนอื่นนี่เป็นคำถามที่ยอดเยี่ยม มักจะมีคำถามเกี่ยวกับการจับคู่รูปแบบบางอย่างยกเว้นในบางบริบท (เช่นภายในบล็อกโค้ดหรือในวงเล็บ) คำถามเหล่านี้มักก่อให้เกิดการแก้ปัญหาที่ค่อนข้างน่าอึดอัดใจ ดังนั้นคำถามของคุณเกี่ยวกับหลายบริบทจึงเป็นความท้าทายพิเศษ
เซอร์ไพรส์
น่าแปลกที่มีโซลูชันที่มีประสิทธิภาพอย่างน้อยหนึ่งวิธีโดยทั่วไปใช้งานง่ายและมีความสุขในการบำรุงรักษา ใช้งานได้กับ regex ทุกรสชาติที่ให้คุณตรวจสอบกลุ่มการจับภาพในโค้ดของคุณ และเกิดขึ้นเพื่อตอบคำถามทั่วไปหลายข้อที่ในตอนแรกอาจฟังดูแตกต่างจากของคุณ: "จับคู่ทุกอย่างยกเว้นโดนัท", "แทนที่ทั้งหมด แต่ ... ", "จับคู่ทุกคำยกเว้นคำที่อยู่ในบัญชีดำของแม่", "ละเว้น แท็ก "," จับคู่อุณหภูมิเว้นแต่ตัวเอียง "...
น่าเศร้าที่เทคนิคนี้ไม่เป็นที่รู้จักกันดี: ฉันประเมินว่าในคำถาม SO ยี่สิบข้อที่สามารถใช้ได้มีเพียงคำตอบเดียวที่กล่าวถึงมันซึ่งหมายความว่าอาจจะหนึ่งในห้าสิบหรือหกสิบคำตอบ ดูการแลกเปลี่ยนของฉันกับ Kobi ในความคิดเห็น เทคนิคนี้ได้อธิบายไว้ในเชิงลึกในบทความนี้ซึ่งเรียกมันว่า (ในแง่ดี) "เคล็ดลับ regex ที่ดีที่สุดเท่าที่เคยมีมา" โดยไม่ต้องลงรายละเอียดมากนักฉันจะพยายามให้คุณเข้าใจอย่างถ่องแท้ว่าเทคนิคนี้ทำงานอย่างไร สำหรับรายละเอียดเพิ่มเติมและตัวอย่างโค้ดในภาษาต่างๆเราขอแนะนำให้คุณศึกษาแหล่งข้อมูลนั้น
รูปแบบที่รู้จักกันดีกว่า
มีรูปแบบที่ใช้ไวยากรณ์เฉพาะสำหรับ Perl และ PHP ที่ทำได้เหมือนกัน คุณจะได้เห็นมันในดังนั้นในมือของโท regex เช่นCasimiretHippolyteและHamza ฉันจะบอกคุณเพิ่มเติมเกี่ยวกับเรื่องนี้ด้านล่าง แต่จุดสนใจของฉันที่นี่คือโซลูชันทั่วไปที่ใช้ได้กับ regex ทุกรสชาติ (ตราบใดที่คุณสามารถตรวจสอบกลุ่มการจับภาพในโค้ดของคุณได้)
ขอบคุณสำหรับความเป็นมา zx81 ... แต่สูตรคืออะไร?
ข้อมูลสำคัญ
วิธีนี้จะส่งคืนการจับคู่ในการจับภาพกลุ่ม 1 ไม่สนใจเลยเกี่ยวกับการแข่งขันโดยรวม
ในความเป็นจริงเคล็ดลับคือการจับคู่บริบทต่างๆที่เราไม่ต้องการ (การผูกโยงบริบทเหล่านี้โดยใช้|
OR / alternation) เพื่อ "ทำให้เป็นกลาง" หลังจากที่ตรงกันทั้งหมดบริบทที่ไม่พึงประสงค์ส่วนสุดท้ายของการสลับตรงกับสิ่งที่เราไม่ต้องการและจับไปยังกลุ่มที่ 1
สูตรทั่วไปคือ
Not_this_context|Not_this_either|StayAway|(WhatYouWant)
สิ่งนี้จะตรงกันNot_this_context
แต่ในแง่หนึ่งการจับคู่จะกลายเป็นถังขยะเพราะเราจะไม่ดูการแข่งขันโดยรวม: เราดูเฉพาะการจับภาพของกลุ่ม 1
ในกรณีของคุณด้วยตัวเลขของคุณและบริบททั้งสามของคุณที่จะละเว้นเราสามารถทำได้:
s1|s2|s3|(\b\d+\b)
โปรดทราบว่าเนื่องจากจริงๆแล้วเราจับคู่ s1, s2 และ s3 แทนที่จะพยายามหลีกเลี่ยงด้วยการค้นหาแบบวนรอบนิพจน์แต่ละรายการสำหรับ s1, s2 และ s3 จึงยังคงชัดเจนเหมือนวัน (เป็นนิพจน์ย่อยในแต่ละด้านของ a |
)
นิพจน์ทั้งหมดสามารถเขียนได้ดังนี้:
(?m)^.*\.$|\([^\)]*\)|if\(.*?//endif|(\b\d+\b)
ดูการสาธิตนี้(แต่เน้นที่กลุ่มการจับภาพในบานหน้าต่างด้านขวาล่าง)
หากคุณพยายามแยกนิพจน์ทั่วไปนี้ที่|
ตัวคั่นแต่ละตัวในใจจริงๆแล้วมันเป็นเพียงชุดของนิพจน์ธรรมดาสี่ชุดเท่านั้น
สำหรับรสชาติที่รองรับการเว้นระยะห่างจะอ่านได้ดีเป็นพิเศษ
(?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)
อ่านและบำรุงรักษาง่ายเป็นพิเศษ
การขยายนิพจน์ทั่วไป
เมื่อคุณต้องการละเว้นสถานการณ์เพิ่มเติม s4 และ s5 คุณเพิ่มในทางเลือกเพิ่มเติมทางด้านซ้าย:
s4|s5|s1|s2|s3|(\b\d+\b)
วิธีนี้ทำงานอย่างไร?
บริบทที่คุณไม่ต้องการจะถูกเพิ่มลงในรายการทางเลือกทางด้านซ้าย: จะตรงกัน แต่จะไม่มีการตรวจสอบการจับคู่โดยรวมเหล่านี้ดังนั้นการจับคู่จึงเป็นวิธีที่จะนำไปไว้ใน "ถังขยะ"
อย่างไรก็ตามเนื้อหาที่คุณต้องการจะถูกบันทึกไว้ในกลุ่ม 1 จากนั้นคุณต้องตรวจสอบโดยทางโปรแกรมว่ากลุ่ม 1 ถูกตั้งค่าไว้และไม่ว่างเปล่า นี่เป็นงานการเขียนโปรแกรมที่ไม่สำคัญ (และเราจะพูดถึงวิธีการทำงานในภายหลัง) โดยเฉพาะอย่างยิ่งเมื่อพิจารณาว่าจะทำให้คุณมี regex ง่ายๆที่คุณสามารถเข้าใจได้อย่างรวดเร็วและแก้ไขหรือขยายได้ตามต้องการ
ฉันไม่ได้เป็นแฟนของการแสดงภาพเสมอไป แต่วิธีนี้ทำได้ดีในการแสดงให้เห็นว่าวิธีการนั้นง่ายเพียงใด "บรรทัด" แต่ละรายการสอดคล้องกับการจับคู่ที่เป็นไปได้ แต่จะจับเฉพาะบรรทัดล่างสุดในกลุ่ม 1
Debuggex Demo
การเปลี่ยนแปลง Perl / PCRE
ตรงกันข้ามกับโซลูชันทั่วไปข้างต้นมีรูปแบบสำหรับ Perl และ PCRE ที่มักเห็นใน SO อย่างน้อยก็อยู่ในมือของ regex Gods เช่น @CasimiretHippolyte และ @HamZa มันคือ:
(?:s1|s2|s3)(*SKIP)(*F)|whatYouWant
ในกรณีของคุณ:
(?m)(?:^.*\.$|\([^()]*\)|if\(.*?//endif)(*SKIP)(*F)|\b\d+\b
รูปแบบนี้ใช้งานได้ง่ายกว่าเล็กน้อยเนื่องจากเนื้อหาที่จับคู่ในบริบท s1, s2 และ s3 นั้นถูกข้ามไปดังนั้นคุณจึงไม่จำเป็นต้องตรวจสอบการจับภาพกลุ่ม 1 (สังเกตว่าวงเล็บจะหายไป) การแข่งขันมีเพียงwhatYouWant
โปรดทราบว่า(*F)
, (*FAIL)
และ(?!)
ทุกคนในสิ่งเดียวกัน หากคุณต้องการปกปิดมากขึ้นคุณสามารถใช้ไฟล์(*SKIP)(?!)
การสาธิตสำหรับเวอร์ชันนี้
การใช้งาน
ต่อไปนี้เป็นปัญหาทั่วไปที่เทคนิคนี้มักจะแก้ได้ง่ายๆ คุณจะสังเกตได้ว่าการเลือกใช้คำอาจทำให้ปัญหาเหล่านี้ฟังดูแตกต่างกันไปในขณะที่ความจริงแล้วปัญหาเหล่านี้แทบจะเหมือนกัน
- ฉันจะตรงกับ foo ยกเว้นใดก็ได้ในแท็กเหมือน
<a stuff...>...</a>
?
- ฉันจะจับคู่ foo ได้อย่างไรยกเว้นใน
<i>
แท็กหรือข้อมูลโค้ดจาวาสคริปต์ (เงื่อนไขเพิ่มเติม)
- ฉันจะจับคู่คำทั้งหมดที่ไม่อยู่ในบัญชีดำนี้ได้อย่างไร?
- ฉันจะเพิกเฉยต่อสิ่งที่อยู่ในบล็อก SUB ... END SUB ได้อย่างไร?
- ฉันจะจับคู่ทุกอย่างยกเว้น ... s1 s2 s3 ได้อย่างไร
วิธีการตั้งโปรแกรมจับภาพกลุ่ม 1
คุณไม่ได้เป็นรหัส แต่เพื่อให้เสร็จสมบูรณ์ ... รหัสในการตรวจสอบกลุ่ม 1 จะขึ้นอยู่กับภาษาที่คุณเลือกอย่างชัดเจน ไม่ว่าในกรณีใดก็ตามไม่ควรเพิ่มเกินสองสามบรรทัดในโค้ดที่คุณจะใช้ตรวจสอบการจับคู่
หากมีข้อสงสัยฉันขอแนะนำให้คุณดูส่วนตัวอย่างโค้ดของบทความที่กล่าวถึงก่อนหน้านี้ซึ่งนำเสนอโค้ดสำหรับภาษาไม่กี่ภาษา
ทางเลือก
ขึ้นอยู่กับความซับซ้อนของคำถามและในเอนจิ้น regex ที่ใช้มีหลายทางเลือก นี่คือสองข้อที่สามารถใช้ได้กับสถานการณ์ส่วนใหญ่รวมถึงเงื่อนไขต่างๆ ในมุมมองของฉันไม่มีอะไรน่าสนใจเท่าs1|s2|s3|(whatYouWant)
สูตรอาหารถ้าเพียงเพราะความชัดเจนมักจะชนะ
1. แทนที่แล้วจับคู่
ทางออกที่ดีที่ฟังดูแฮ็ค แต่ทำงานได้ดีในหลาย ๆ สภาพแวดล้อมคือการทำงานในสองขั้นตอน regex แรกทำให้บริบทเป็นกลางที่คุณต้องการละเว้นโดยการแทนที่สตริงที่อาจขัดแย้งกัน หากคุณต้องการจับคู่เท่านั้นคุณสามารถแทนที่ด้วยสตริงว่างจากนั้นเรียกใช้การจับคู่ของคุณในขั้นตอนที่สอง @@@
หากคุณต้องการที่จะเปลี่ยนคุณเป็นครั้งแรกสามารถแทนที่สตริงที่จะละเลยกับสิ่งที่โดดเด่นเช่นรอบตัวเลขของคุณกับห่วงโซ่ความกว้างคงที่ของ หลังจากการเปลี่ยนครั้งนี้คุณมีอิสระที่จะแทนที่สิ่งที่คุณต้องการจริงๆจากนั้นคุณจะต้องเปลี่ยนกลับ@@@
สตริง เฉพาะของคุณ
2. Lookarounds
โพสต์ต้นฉบับของคุณแสดงให้เห็นว่าคุณเข้าใจวิธีการยกเว้นเงื่อนไขเดียวโดยใช้การค้นหา คุณบอกว่า C # ดีมากสำหรับสิ่งนี้และคุณพูดถูก แต่ไม่ใช่ตัวเลือกเดียว NET regex รสชาติที่พบใน C #, VB.NET และ Visual C ++ เช่นเดียวกับregex
โมดูลที่ยังคงทดลองเพื่อแทนที่re
ใน Python เป็นเพียงสองเอ็นจิ้นที่ฉันรู้ว่ารองรับการมองแบบไม่มีที่สิ้นสุด ด้วยเครื่องมือเหล่านี้เงื่อนไขเดียวในรูปลักษณ์เดียวเบื้องหลังสามารถดูแลไม่เพียง แต่มองข้างหลัง แต่ยังรวมถึงการแข่งขันและนอกเหนือจากการแข่งขันโดยไม่ต้องประสานงานกับผู้มอง เงื่อนไขเพิ่มเติม? การค้นหาเพิ่มเติม
การรีไซเคิล regex ที่คุณมีสำหรับ s3 ใน C # รูปแบบทั้งหมดจะเป็นแบบนี้
(?!.*\.)(?<!\([^()]*(?=\d+[^)]*\)))(?<!if\(\D*(?=\d+.*?//endif))\b\d+\b
แต่ตอนนี้คุณก็รู้ว่าฉันไม่แนะนำสิ่งนี้ใช่ไหม?
การลบ
@HamZa และ @Jerry WhatYouWant
ได้แนะนำให้ผมพูดถึงเคล็ดลับเพิ่มเติมสำหรับกรณีเมื่อคุณพยายามที่จะเพียงแค่ลบ คุณจำได้ว่าสูตรที่จะจับคู่WhatYouWant
(จับเป็นกลุ่ม 1) คือs1|s2|s3|(WhatYouWant)
ใช่ไหม? ในการลบอินสแตนซ์ทั้งหมดWhatYouWant
คุณเปลี่ยน regex เป็น
(s1|s2|s3)|WhatYouWant
$1
สำหรับสตริงทดแทนคุณใช้ สิ่งที่เกิดขึ้นที่นี่คือสำหรับแต่ละอินสแตนซ์s1|s2|s3
ที่ตรงกันการแทนที่จะ$1
แทนที่อินสแตนซ์นั้นด้วยตัวมันเอง (อ้างอิงโดย$1
) ในทางกลับกันเมื่อWhatYouWant
มีการจับคู่มันจะถูกแทนที่ด้วยกลุ่มว่างเปล่าและไม่มีอะไรอื่น - ดังนั้นจึงถูกลบ ดูการสาธิตนี้ขอบคุณ @HamZa และ @Jerry ที่แนะนำเพิ่มเติมที่ยอดเยี่ยมนี้
การเปลี่ยน
สิ่งนี้นำเราไปสู่การเปลี่ยนซึ่งฉันจะพูดสั้น ๆ
- เมื่อแทนที่โดยไม่มีอะไรให้ดูเคล็ดลับ "การลบ" ด้านบน
- เมื่อเปลี่ยนหากใช้ Perl หรือ PCRE ให้ใช้
(*SKIP)(*F)
รูปแบบที่กล่าวถึงข้างต้นเพื่อให้ตรงกับสิ่งที่คุณต้องการและทำการแทนที่แบบตรง
- ในรสชาติอื่น ๆ ภายในการเรียกฟังก์ชันการแทนที่ให้ตรวจสอบการจับคู่โดยใช้การเรียกกลับหรือแลมบ์ดาและแทนที่หากมีการตั้งค่ากลุ่ม 1 หากคุณต้องการความช่วยเหลือบทความที่อ้างถึงแล้วจะให้รหัสแก่คุณในภาษาต่างๆ
มีความสุข!
ไม่รอยังมีอีก!
อาไม่ฉันจะเก็บบันทึกไว้เป็นความทรงจำของฉันในยี่สิบเล่มเพื่อออกในฤดูใบไม้ผลิปีหน้า
\K
ไม่ใช่ไวยากรณ์ php พิเศษ กรุณาอธิบายและชี้แจงสิ่งที่คุณต้องการจะพูด หากคุณตั้งเป้าหมายที่จะบอกเราว่าคุณไม่ต้องการวิธีแก้ปัญหาที่ "ซับซ้อน" คุณต้องบอกว่าอะไรซับซ้อนสำหรับคุณและทำไม