นิพจน์ทั่วไป Balancing Groups คืออะไร


92

ฉันเพิ่งอ่านคำถามเกี่ยวกับวิธีรับข้อมูลภายในวงเล็บปีกกาสองชั้น ( คำถามนี้ ) จากนั้นก็มีคนนำกลุ่มสมดุลขึ้นมา ฉันยังไม่ค่อยแน่ใจว่ามันคืออะไรและจะใช้อย่างไร

ฉันอ่านผ่านBalancing Group Definitionแต่คำอธิบายนั้นยากที่จะปฏิบัติตามและฉันก็ยังค่อนข้างสับสนกับคำถามที่ฉันพูดถึง

ใครช่วยอธิบายได้ง่ายๆว่ากลุ่มสมดุลคืออะไรและมีประโยชน์อย่างไร


ฉันสงสัยว่าจริงๆแล้วรองรับ regex Engiens ได้กี่ตัว
Mike de Klerk

2
@MikedeKlerk ได้รับการสนับสนุนในเอ็นจิ้น. NET Regex เป็นอย่างน้อย
It'sNotALie

คำตอบ:


175

เท่าที่ฉันรู้กลุ่มการปรับสมดุลเป็นลักษณะเฉพาะของรสชาติ regex ของ. NET

นอกเหนือ: กลุ่มที่ทำซ้ำ

ขั้นแรกคุณต้องรู้ว่า. NET เป็น (อีกครั้งเท่าที่ฉันรู้) รสชาติ regex เดียวที่ช่วยให้คุณเข้าถึงการจับภาพหลายกลุ่มของกลุ่มการจับภาพเดียว (ไม่ใช่ในการอ้างอิงย้อนกลับ แต่หลังจากการจับคู่เสร็จสิ้น)

เพื่อแสดงสิ่งนี้เป็นตัวอย่างให้พิจารณารูปแบบ

(.)+

"abcd"และสตริง

ในรสชาติ regex อื่น ๆ การจับกลุ่ม1จะให้ผลลัพธ์เดียว: d(โปรดทราบว่าการจับคู่ทั้งหมดจะเป็นไปabcdตามที่คาดไว้) เนื่องจากการใช้กลุ่มการบันทึกใหม่ทุกครั้งจะเขียนทับการจับภาพก่อนหน้า

ในทางกลับกัน. NET จะจำได้ทั้งหมด และมันก็ทำในกอง หลังจากจับคู่ regex ข้างต้นเช่น

Match m = new Regex(@"(.)+").Match("abcd");

คุณจะพบว่า

m.Groups[1].Captures

เป็นCaptureCollectionองค์ประกอบที่สอดคล้องกับการจับภาพทั้งสี่

0: "a"
1: "b"
2: "c"
3: "d"

โดยที่ตัวเลขเป็นดัชนีในCaptureCollection. ดังนั้นโดยทั่วไปทุกครั้งที่มีการใช้กลุ่มอีกครั้งการจับภาพใหม่จะถูกผลักเข้าไปในสแต็ก

จะน่าสนใจยิ่งขึ้นหากเราใช้กลุ่มการจับภาพที่ตั้งชื่อ เนื่องจาก. NET อนุญาตให้ใช้ชื่อเดิมซ้ำเราจึงสามารถเขียน regex ได้เช่น

(?<word>\w+)\W+(?<word>\w+)

เพื่อจับคำสองคำให้อยู่ในกลุ่มเดียวกัน อีกครั้งทุกครั้งที่พบกลุ่มที่มีชื่อเฉพาะการจับภาพจะถูกผลักเข้าไปในสแต็ก ดังนั้นการใช้ regex นี้กับอินพุต"foo bar"และการตรวจสอบ

m.Groups["word"].Captures

เราพบภาพสองภาพ

0: "foo"
1: "bar"

สิ่งนี้ช่วยให้เราสามารถผลักสิ่งต่างๆไปยังกองเดียวจากส่วนต่างๆของนิพจน์ได้ แต่ยังคงเป็นเพียงคุณลักษณะ .NET CaptureCollectionของความสามารถในการติดตามจับหลายที่มีการระบุไว้ในนี้ แต่ผมบอกว่าคอลเลกชันนี้เป็นสแต็ค ดังนั้นเราจึงสามารถปรากฏ สิ่งจากมันได้หรือไม่

ป้อน: การปรับสมดุลกลุ่ม

ปรากฎว่าเราทำได้ ถ้าเราใช้กลุ่มที่ชอบ(?<-word>...)การจับภาพสุดท้ายจะถูกดึงออกมาจากสแต็กwordหากนิพจน์ย่อย...ตรงกัน ดังนั้นถ้าเราเปลี่ยนนิพจน์ก่อนหน้าเป็น

(?<word>\w+)\W+(?<-word>\w+)

จากนั้นกลุ่มที่สองจะปรากฏการจับภาพของกลุ่มแรกและเราจะได้รับช่องว่างCaptureCollectionในตอนท้าย แน่นอนว่าตัวอย่างนี้ไม่มีประโยชน์เลย

แต่ยังมีรายละเอียดอีกอย่างหนึ่งของไวยากรณ์ลบ: หากสแต็กว่างเปล่าอยู่แล้วกลุ่มจะล้มเหลว (โดยไม่คำนึงถึงรูปแบบย่อย) เราสามารถใช้ประโยชน์จากพฤติกรรมนี้เพื่อนับระดับการซ้อน - และนี่คือที่มาของกลุ่มการปรับสมดุลชื่อ (และจุดที่น่าสนใจ) สมมติว่าเราต้องการจับคู่สตริงที่อยู่ในวงเล็บอย่างถูกต้อง เราดันวงเล็บเปิดแต่ละอันบนสแต็กและใส่หนึ่งการบันทึกสำหรับแต่ละวงเล็บปิด หากเราพบวงเล็บปิดหนึ่งรายการมากเกินไปมันจะพยายามเปิดสแต็กว่างและทำให้รูปแบบล้มเหลว:

^(?:[^()]|(?<Open>[(])|(?<-Open>[)]))*$

ดังนั้นเราจึงมีสามทางเลือกในการทำซ้ำ ทางเลือกแรกใช้ทุกอย่างที่ไม่ใช่วงเล็บ ทางเลือกที่สองจะจับคู่(s ในขณะที่ผลักมันลงบนสแต็ก ทางเลือกที่สามตรงกับ)s ในขณะที่ดึงองค์ประกอบจากสแต็ก (ถ้าเป็นไปได้!)

หมายเหตุ:เพื่อชี้แจงเราเพียงตรวจสอบว่าไม่มีวงเล็บที่ไม่ตรงกัน! ซึ่งหมายความว่าสตริงที่ไม่มีวงเล็บจะตรงกันเนื่องจากยังคงมีความถูกต้องทางไวยากรณ์ (ในบางไวยากรณ์ที่คุณต้องใช้วงเล็บเพื่อให้ตรงกัน) หากคุณต้องการตรวจสอบให้แน่ใจว่ามีวงเล็บอย่างน้อยหนึ่งชุดเพียงแค่เพิ่ม lookahead (?=.*[(])หลังไฟล์^.

แม้ว่ารูปแบบนี้จะไม่สมบูรณ์แบบ (หรือถูกต้องทั้งหมด)

ตอนจบ: รูปแบบตามเงื่อนไข

มีการจับอีกหนึ่งรายการ: สิ่งนี้ไม่แน่ใจว่าสแต็กว่างเปล่าที่ส่วนท้ายของสตริง (ดังนั้นจึง(foo(bar)ใช้ได้) .NET (และรสชาติอื่น ๆ อีกมากมาย) มีอีกหนึ่งโครงสร้างที่ช่วยเราได้นั่นคือรูปแบบเงื่อนไข ไวยากรณ์ทั่วไปคือ

(?(condition)truePattern|falsePattern)

โดยที่falsePatternเป็นทางเลือก - หากละเว้นกรณีเท็จจะจับคู่กันเสมอ เงื่อนไขอาจเป็นรูปแบบหรือชื่อของกลุ่มการจับภาพ ผมจะเน้นไปที่กรณีหลังตรงนี้ หากเป็นชื่อของกลุ่มการจับภาพระบบtruePatternจะใช้ก็ต่อเมื่อกองการจับภาพสำหรับกลุ่มนั้นไม่ว่างเปล่า นั่นคือรูปแบบที่มีเงื่อนไขเช่น(?(name)yes|no)อ่าน "ถ้าnameมีการจับคู่และจับบางสิ่งบางอย่าง (ที่ยังคงเป็นในกอง) รูปแบบการใช้งานyesอย่างอื่นใช้รูปแบบno"

ดังนั้นในตอนท้ายของรูปแบบด้านบนของเราเราสามารถเพิ่มบางสิ่ง(?(Open)failPattern)ที่ทำให้รูปแบบทั้งหมดล้มเหลวได้ถ้าOpen-stack ไม่ว่างเปล่า สิ่งที่ง่ายที่สุดในการทำให้รูปแบบล้มเหลวโดยไม่มีเงื่อนไขคือ(?!)(การมองเชิงลบที่ว่างเปล่า) ดังนั้นเราจึงมีรูปแบบสุดท้ายของเรา:

^(?:[^()]|(?<Open>[(])|(?<-Open>[)]))*(?(Open)(?!))$

โปรดทราบว่าไวยากรณ์แบบมีเงื่อนไขนี้ไม่มีส่วนเกี่ยวข้องกับการปรับสมดุลกลุ่ม แต่จำเป็นต้องควบคุมพลังทั้งหมดของพวกเขา

จากที่นี่ท้องฟ้ามีขีด จำกัด การใช้งานที่ซับซ้อนมากเป็นไปได้และมี gotcha บางตัวเมื่อใช้ร่วมกับคุณสมบัติอื่น ๆ . NET-Regex เช่นรูปลักษณ์ที่มีความยาวผันแปรได้ ( ซึ่งฉันต้องเรียนรู้วิธีที่ยากลำบากด้วยตัวเอง ) อย่างไรก็ตามคำถามหลักคือรหัสของคุณยังคงสามารถรักษาได้เมื่อใช้คุณสมบัติเหล่านี้หรือไม่? คุณต้องจัดทำเอกสารเป็นอย่างดีและต้องแน่ใจว่าทุกคนที่ทำงานกับมันรับทราบถึงคุณสมบัติเหล่านี้ด้วย มิฉะนั้นคุณอาจจะดีกว่าเพียงแค่เดินสตริงด้วยตนเองทีละอักขระและนับระดับการซ้อนกันเป็นจำนวนเต็ม

ภาคผนวก: (?<A-B>...)ไวยากรณ์คืออะไร?

เครดิตสำหรับส่วนนี้ไปที่ Kobi (ดูคำตอบด้านล่างสำหรับรายละเอียดเพิ่มเติม)

จากทั้งหมดข้างต้นเราสามารถตรวจสอบความถูกต้องว่าสตริงได้รับการจัดวงเล็บอย่างถูกต้อง แต่มันจะมีประโยชน์กว่านี้มากถ้าเราสามารถจับภาพ (ซ้อนกัน) สำหรับเนื้อหาทั้งหมดในวงเล็บเหล่านั้นได้ แน่นอนว่าเราจำวงเล็บเปิดและปิดในสแต็กการดักจับแยกต่างหากที่ไม่ได้ว่างเปล่าจากนั้นทำการแยกสตริงย่อยตามตำแหน่งในขั้นตอนแยกต่างหาก

แต่. NET มีคุณสมบัติอำนวยความสะดวกอีกอย่างหนึ่งที่นี่: ถ้าเราใช้(?<A-B>subPattern)ไม่เพียง แต่การจับภาพที่โผล่ออกมาจากสแต็Bกเท่านั้น แต่ยังรวมถึงทุกอย่างระหว่างการจับภาพที่โผล่ขึ้นมาBและกลุ่มปัจจุบันนี้จะถูกผลักไปยังสแต็Aก ดังนั้นหากเราใช้กลุ่มแบบนี้สำหรับวงเล็บปิดในขณะที่สร้างระดับการซ้อนจากสแต็กเราสามารถดันเนื้อหาของทั้งคู่ไปยังสแต็กอื่นได้:

^(?:[^()]|(?<Open>[(])|(?<Content-Open>[)]))*(?(Open)(?!))$

Kobi ให้Live-Demo นี้ในคำตอบของเขา

ดังนั้นการรวบรวมสิ่งเหล่านี้ทั้งหมดเข้าด้วยกันเราสามารถ:

  • จำการจับภาพจำนวนมากโดยพลการ
  • ตรวจสอบโครงสร้างที่ซ้อนกัน
  • จับแต่ละระดับการซ้อน

ทั้งหมดในนิพจน์ทั่วไปเดียว ถ้ามันไม่น่าตื่นเต้น ... ;)

แหล่งข้อมูลบางอย่างที่ฉันพบว่ามีประโยชน์เมื่อได้เรียนรู้ครั้งแรก:


7
คำตอบนี้ได้ถูกเพิ่มเข้าไปในคำถามที่พบบ่อยเกี่ยวกับนิพจน์ทั่วไปของStack Overflowภายใต้ "Advanced Regex-Fu"
aliteralmind

40

เพียงเล็กน้อยสำหรับคำตอบที่ยอดเยี่ยมของ M. Buettner:

การจัดการกับ(?<A-B>)ไวยากรณ์คืออะไร?

(?<A-B>x)แตกต่างจาก(?<-A>(?<B>x)). ทำให้เกิดโฟลว์การควบคุมเดียวกัน*แต่จับภาพต่างกัน
ตัวอย่างเช่นลองดูรูปแบบการจัดฟันแบบสมดุล:

(?:[^{}]|(?<B>{)|(?<-B>}))+(?(B)(?!))

ในตอนท้ายของการแข่งขันเรามีสตริงที่สมดุล แต่นั่นคือทั้งหมดที่เรามี - เราไม่รู้ว่าวงเล็บปีกกาอยู่ที่ไหนเพราะBสแต็กว่างเปล่า งานหนักที่เครื่องยนต์ทำเพื่อเราหายไป
( ตัวอย่างใน Regex Storm )

(?<A-B>x)เป็นทางออกสำหรับปัญหานั้น อย่างไร? มันไม่ได้จับxเข้า$A: มันจับเนื้อหาระหว่างการจับกุมก่อนหน้านี้Bและตำแหน่งปัจจุบัน

มาใช้ในรูปแบบของเรา:

(?:[^{}]|(?<Open>{)|(?<Content-Open>}))+(?(Open)(?!))

สิ่งนี้จะจับเข้า$Contentกับสตริงระหว่างวงเล็บปีกกา (และตำแหน่ง) สำหรับแต่ละคู่ตลอดทาง
สำหรับสตริง{1 2 {3} {4 5 {6}} 7}มีต้องการจะจับสี่: 3, 6, 4 5 {6}และ1 2 {3} {4 5 {6}} 7- ดีกว่าไม่มีอะไร} } } }หรือ
( ตัวอย่าง - คลิกที่tableแท็บแล้วดู${Content}จับภาพ )

ในความเป็นจริงสามารถใช้งานได้โดยไม่ต้องปรับสมดุลเลย: (?<A>).(.(?<Content-A>).)จับอักขระสองตัวแรกแม้ว่าจะแยกตามกลุ่มก็ตาม
(มักใช้ Lookahead มากกว่าที่นี่ แต่ไม่ได้ปรับขนาดเสมอไป: อาจซ้ำกันตรรกะของคุณ)

(?<A-B>)เป็นคุณสมบัติที่แข็งแกร่ง - ช่วยให้คุณควบคุมการจับภาพได้อย่างแม่นยำ โปรดจำไว้ว่าเมื่อคุณพยายามใช้รูปแบบของคุณมากขึ้น


@FYI ดำเนินการอภิปรายต่อจากคำถามที่คุณไม่ชอบในคำตอบใหม่ของคำถามนี้ :)
zx81

ฉันกำลังพยายามหาวิธีที่จะทำการตรวจสอบ regex ของวงเล็บปีกกาที่สมดุลโดยไม่ต้องใส่เครื่องหมายวงเล็บภายใน เช่นรหัสต่อไปนี้จะผ่าน: คลาสสาธารณะ Foo {ส่วนตัว const char BAR = '{'; สตริงส่วนตัว _qux = "{{{"; } มีใครทำแบบนี้บ้าง?
Mr Anderson

@MrAnderson - คุณเพียงแค่ต้องเพิ่ม|'[^']*'ในสถานที่ที่เหมาะสม: ตัวอย่างเช่น หากคุณยังต้องหนีตัวละครมีตัวอย่างที่นี่: (Regex สำหรับการจับคู่ตัวอักษร C # สตริง) [ stackoverflow.com/a/4953878/7586]
Kobi
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.