เคล็ดลับสำหรับ Regex Golf


43

คล้ายกับหัวข้อของเราสำหรับเคล็ดลับการเล่นกอล์ฟเฉพาะภาษา: เทคนิคทั่วไปในการย่อนิพจน์ทั่วไปคืออะไร

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

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


การส่งเสริมตนเองที่เห็นได้ชัด: สิ่งที่ประเภทของการใช้งาน regex นี้ตกอยู่ใน? codegolf.stackexchange.com/a/37685/8048
Kyle Strand

@ KyleStrand "นิพจน์ทั่วไปที่ใช้เป็นส่วนหนึ่งของรหัส golfed ที่ใหญ่กว่า"
Martin Ender

คำตอบ:


24

เมื่อไม่หลบหนี

กฎเหล่านี้ใช้กับรสชาติส่วนใหญ่ถ้าไม่ใช่ทั้งหมด:

  • ] ไม่จำเป็นต้องหลบหนีเมื่อไม่มีใครเทียบ

  • {และ}ไม่จำเป็นต้องหลบหนีเมื่อไม่ได้เป็นส่วนหนึ่งของการทำซ้ำเช่นการ{a}จับคู่อย่าง{a}แท้จริง แม้ว่าคุณต้องการเพื่อให้ตรงกับสิ่งที่ชอบ{2}, {2\}คุณจะต้องหลบหนีหนึ่งของพวกเขาเช่น

ในชั้นเรียนตัวละคร:

  • ]ไม่จำเป็นต้องหลบหนีเมื่อมันเป็นตัวอักษรตัวแรกในชุดตัวอักษรเช่น[]abc]ตรงกับหนึ่ง]abcหรือเมื่อมันเป็นตัวละครที่สองหลังจากที่^เช่น[^]]ตรงกับอะไร ]แต่ (ข้อยกเว้นที่น่าสังเกต: ECMAScript รส!)

  • [ไม่จำเป็นต้องหลบหนีเลย [][]ร่วมกับเคล็ดลับข้างต้นนี้หมายความว่าคุณสามารถจับคู่วงเล็บทั้งที่มีตัวอักษรระดับอย่างน่ากลัวเคาน์เตอร์

  • ^ไม่จำเป็นต้องหลบหนีเมื่อมันไม่ได้เป็น[ab^c]ตัวอักษรตัวแรกในชุดตัวอักษรเช่น

  • -ไม่จำเป็นต้องหลบหนีเมื่อมันทั้งเป็นครั้งแรก (ที่สองหลังจากที่^) หรือตัวอักษรตัวสุดท้ายในชุดตัวอักษรเช่น[-abc], หรือ[^-abc][abc-]

  • ไม่ต้องใช้อักขระอื่นในการยกเว้นภายในคลาสอักขระแม้ว่าจะเป็นอักขระเมตานอกคลาสอักขระ (ยกเว้นเครื่องหมายแบ็กสแลช\เอง)

นอกจากนี้ในบางรสชาติ^และ$มีการจับคู่อย่างแท้จริงเมื่อพวกเขาไม่ได้อยู่ที่จุดเริ่มต้นหรือจุดสิ้นสุดของ regex ตามลำดับ

(ขอบคุณ @ MartinBüttnerสำหรับการกรอกรายละเอียดเล็กน้อย)


บางคนชอบที่จะหลบหนีจุดที่เกิดขึ้นจริงโดยใส่มันลงในคลาสของตัวละครที่ไม่ต้องหลบหนี (เช่น[.]) การหลบหนีตามปกติจะช่วยประหยัด 1 ไบต์ในกรณีนี้\.
CSᵠ

โปรดทราบว่า[จะต้องหลบหนีใน Java ไม่แน่ใจเกี่ยวกับ ICU (ใช้ใน Android และ iOS) หรือ. NET
n̴̖̋h̷͉̃a̷̭̿h̸̡̅ẗ̵̨́d̷̰̀ĥ̷̳

18

นิพจน์ทั่วไปอย่างง่ายเพื่อจับคู่อักขระที่พิมพ์ได้ทั้งหมดในตารางASCII

[ -~]

1
สุดยอดบริสุทธิ์ตัวอักษรทั้งหมดจากแป้นพิมพ์มาตรฐานของสหรัฐอเมริกา! หมายเหตุ: ตาราง ascii มาตรฐาน (ไม่รวมช่วงขยาย 127-255
CSᵠ

ฉันใช้บ่อย แต่ไม่มีอักขระ "ปกติ" ทั่วไป: TAB และสมมติว่าคุณใช้ LC_ALL = "C" (หรือคล้ายกัน) เนื่องจากตำแหน่งที่ตั้งอื่น ๆ จะล้มเหลว
Olivier Dulac

สามารถใช้ยัติภังค์เช่นนั้นเพื่อระบุช่วงของอักขระใด ๆ ในตาราง ASCII ได้หรือไม่? ใช้งานได้กับ regex ทั้งหมดหรือไม่
Josh Withee

14

รู้ว่ารสชาติของคุณ regex

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

Perl และ PCRE

ฉันกำลังโยนสิ่งเหล่านี้ลงในหม้อใบเดียวเนื่องจากฉันไม่คุ้นเคยกับรสชาติ Perl และพวกเขาส่วนใหญ่เทียบเท่ากัน (PCRE ใช้สำหรับการแสดงผลปกติที่เข้ากันได้กับ Perl) ข้อได้เปรียบหลักของรสชาติ Perl คือคุณสามารถเรียกรหัส Perl ได้จากภายใน regex และการทดแทน

  • recursion / ซับรูทีน อาจเป็นคุณลักษณะที่สำคัญที่สุดสำหรับการเล่นกอล์ฟ (ซึ่งมีอยู่ในสองรสชาติเท่านั้น)
  • (?(group)yes|no)รูปแบบตามเงื่อนไข
  • รองรับการเปลี่ยนแปลงของคดีในสตริงทดแทนด้วย\l, \u, และ\L\U
  • PCRE อนุญาตให้มีการสลับใน lookbehinds ซึ่งแต่ละทางเลือกสามารถมีความยาวแตกต่างกัน (แต่คงที่) (รสชาติส่วนใหญ่รวมถึง Perl ต้องใช้ lookbehinds เพื่อให้มีความยาวคงที่โดยรวม)
  • \G เพื่อยึดการแข่งขันให้สิ้นสุดการแข่งขันก่อนหน้า
  • \K เพื่อรีเซ็ตการเริ่มต้นของการแข่งขัน
  • PCRE สนับสนุนทั้งUnicode คุณสมบัติตัวอักษรและสคริปต์
  • \Q...\Eเพื่อหลีกเลี่ยงการวิ่งของตัวละครอีกต่อไป มีประโยชน์เมื่อคุณพยายามจับคู่สตริงที่มีอักขระเมตาจำนวนมาก

.สุทธิ

นี่อาจเป็นรสชาติที่ทรงพลังที่สุดโดยมีข้อบกพร่องเพียงเล็กน้อยเท่านั้น

ข้อบกพร่องที่สำคัญอย่างหนึ่งในแง่ของการเล่นกอล์ฟก็คือมันไม่รองรับปริมาณที่เป็นเจ้าของเช่นรสชาติอื่น ๆ แทนการที่คุณจะต้องเขียน.?+(?>.?)

ชวา

  • เนื่องจากข้อผิดพลาด (ดูภาคผนวก) Java รองรับประเภท lookbehind ความยาวตัวแปรที่ จำกัด : คุณสามารถดูตลอดทางจนถึงจุดเริ่มต้นของสตริงด้วย.*จากที่ที่คุณสามารถเริ่ม lookahead เช่น(?<=(?=lookahead).*)นี้
  • รองรับการรวมและแยกส่วนของคลาสอักขระ
  • มีการสนับสนุนอย่างกว้างขวางมากที่สุดสำหรับ Unicode กับตัวละครคลาสสำหรับ"สคริปต์ Unicode บล็อกประเภทและคุณสมบัติไบนารี"
  • \Q...\E เช่นเดียวกับใน Perl / PCRE

ทับทิม

ในรุ่นล่าสุดรสชาตินี้มีประสิทธิภาพเช่นเดียวกับ PCRE รวมถึงการรองรับการเรียกรูทีนย่อย เช่นเดียวกับ Java มันยังสนับสนุนการรวมและแยกส่วนของคลาสอักขระ คุณสมบัติพิเศษอย่างหนึ่งคือคลาสอักขระในตัวสำหรับเลขฐานสิบหก: \h(และเมื่อตะกี้\H)

คุณลักษณะที่มีประโยชน์ที่สุดสำหรับการเล่นกอล์ฟคือวิธีที่ Ruby จัดการปริมาณ ที่สะดุดตาที่สุดคือความเป็นไปได้ที่จะสร้างรังของปริมาณโดยไม่มีวงเล็บ .{5,7}+ทำงาน.{3}?ได้ดี นอกจากนี้เมื่อเทียบกับรสชาติอื่น ๆ ส่วนใหญ่ถ้าผูกพันลดลงในปริมาณคือ0มันสามารถละเว้นเช่นเทียบเท่ากับ.{,5}.{0,5}

สำหรับรูทีนย่อยความแตกต่างที่สำคัญระหว่างรูทีนย่อยของ PCRE กับรูทีนย่อยของรูบี้ก็คือไวยากรณ์ของรูบี้นั้นเป็นไบต์ที่ยาวกว่า(?n)และ\g<n>มีรูทีนย่อยของรูบี้ แต่สามารถใช้รูทีนย่อยของรูบี้ได้

ในที่สุดทับทิมมีความหมายที่แตกต่างกันสำหรับตัวดัดแปลงที่เกี่ยวข้องกับสายกว่ารสชาติอื่น ๆ ส่วนใหญ่ โมดิฟายเออร์ที่มักจะเรียกmในรสชาติอื่น ๆ จะเปิดอยู่เสมอใน Ruby ดังนั้น^และ$ตรงกับจุดเริ่มต้นและจุดสิ้นสุดของสายเสมอไม่ใช่แค่จุดเริ่มต้นและจุดสิ้นสุดของสตริง วิธีนี้จะช่วยให้คุณประหยัดไบต์ได้หากคุณต้องการพฤติกรรมนี้ แต่มันจะทำให้คุณต้องเสียค่าใช้จ่ายเป็นไบต์เพิ่มเติมหากคุณไม่ต้องการเพราะคุณจะต้องแทนที่^และแทนที่$ด้วย\Aและ\zตามลำดับ นอกจากนั้นตัวดัดแปลงที่มักจะเรียกว่าs(ซึ่งทำให้การ.จับคู่ linefeeds) ถูกเรียกmใน Ruby แทน สิ่งนี้จะไม่ส่งผลกระทบต่อจำนวนไบต์ แต่ควรจำไว้เพื่อหลีกเลี่ยงความสับสน

หลาม

Python มีรสชาติที่ดี แต่ฉันไม่ได้ตระหนักถึงคุณสมบัติที่มีประโยชน์อย่างยิ่งที่คุณจะไม่พบในที่อื่น

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

ECMAScript

รสชาติ ECMAScript มี จำกัด มากและไม่ค่อยมีประโยชน์สำหรับการเล่นกอล์ฟมากนัก สิ่งเดียวที่จะเกิดขึ้นกับมันคือคลาสอักขระว่างเมื่อตะกี้ [^]เพื่อจับคู่อักขระใด ๆ และคลาสอักขระว่างเปล่าที่ล้มเหลวโดยไม่มีเงื่อนไข[](ตรงข้ามกับปกติ(?!)) แต่น่าเสียดายที่รสชาติไม่ได้มีคุณสมบัติใด ๆ ซึ่งทำให้หลังมีประโยชน์สำหรับปัญหาปกติ

Lua

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

  • มีชวเลขเป็นจำนวนมากสำหรับคลาสของอักขระในตัวรวมถึงเครื่องหมายวรรคตอนอักขระตัวพิมพ์ใหญ่ / ตัวพิมพ์เล็กและเลขฐานสิบหก
  • ด้วย%bมันรองรับไวยากรณ์ขนาดกะทัดรัดมากเพื่อให้ตรงกับสตริงที่สมดุล เช่น%b()จับคู่ a (และจากนั้นทุกอย่างจนถึงการจับคู่)(ข้ามคู่จับคู่ด้านในอย่างถูกต้อง) (และ)สามารถเป็นอักขระสองตัวที่นี่

การส่งเสริม

รสชาติ Regex ของ Boostเป็นหลักของ Perl แต่ก็มีคุณสมบัติใหม่บางอย่างดีเพื่อทดแทน regex รวมทั้งการเปลี่ยนแปลงกรณีและเงื่อนไข อันหลังนั้นมีเอกลักษณ์ในการเพิ่มพลังเท่าที่ฉันรู้


โปรดทราบว่าการมองไปข้างหน้าในการมองด้านหลังจะเจาะทะลุขีด จำกัด ที่ถูกมองข้าม ทดสอบใน Java และ PCRE
n̴̖̋h̷͉̃a̷̭̿h̸̡̅ẗ̵̨́d̷̰̀ĥ̷̳

ไม่.?+เทียบเท่ากับ.*?
CalculatorFeline

@CalculatorFeline ในอดีตเป็นปริมาณ 0 หรือ 1 ที่เป็นเจ้าของ (ในรสชาติที่รองรับปริมาณครอบครอง), หลังเป็น 0 หรือมากกว่าปริมาณ
Martin Ender

@CalculatorFeline ah ฉันเข้าใจความสับสน มีการพิมพ์ผิด
Martin Ender

13

รู้ว่าตัวละครของคุณเรียน

รสชาติ Regex ส่วนใหญ่มีคลาสอักขระที่กำหนดไว้ล่วงหน้า ยกตัวอย่างเช่นตรงกับหลักทศนิยมซึ่งเป็นสามไบต์สั้นกว่า\d [0-9]ใช่พวกเขาอาจแตกต่างกันเล็กน้อยซึ่ง\dอาจตรงกับตัวเลข Unicode เช่นกันในบางรสชาติ แต่สำหรับความท้าทายส่วนใหญ่สิ่งนี้จะไม่สร้างความแตกต่าง

นี่คือคลาสอักขระบางตัวที่พบในรสชาติของ regex ส่วนใหญ่:

\d      Match a decimal digit character
\s      Match a whitespace character
\w      Match a word character (typically [a-zA-Z0-9_])

นอกจากนี้เรายังมี:

\D \S \W

ซึ่งเป็นรุ่นเมื่อตะกี้ของข้างต้น

ให้แน่ใจว่าได้ตรวจสอบรสนิยมของคุณสำหรับคลาสตัวละครเพิ่มเติมใด ๆ ที่มันอาจมี ยกตัวอย่างเช่น PCRE มี\Rการขึ้นบรรทัดใหม่และLuaยังมีคลาสเช่นตัวพิมพ์เล็กและตัวพิมพ์ใหญ่

(ขอบคุณ @HamZa และ @ MartinBüttnerสำหรับการชี้สิ่งเหล่านี้)


3
\Rสำหรับการขึ้นบรรทัดใหม่ใน PCRE
HamZa

12

อย่าไปสนใจกับกลุ่มที่ไม่ได้จับภาพ (ยกเว้น ... )

เคล็ดลับนี้ใช้กับ (อย่างน้อย) รสชาติที่ได้รับแรงบันดาลใจจาก Perl ทั้งหมด

สิ่งนี้อาจชัดเจน แต่ (เมื่อไม่เล่นกอล์ฟ) ควรใช้กลุ่มที่ไม่ได้จับภาพ(?:...)เมื่อใดก็ตามที่เป็นไปได้ อักขระพิเศษสองตัว?:นี้สิ้นเปลืองเมื่อเล่นกอล์ฟดังนั้นให้ใช้กลุ่มจับภาพแม้ว่าคุณจะไม่ได้กลับมาเล่นซ้ำก็ตาม

มีอยู่คนหนึ่ง (หายาก) ยกเว้น แต่เป็น: ถ้าคุณเกิดขึ้นกับกลุ่ม backreference 10อย่างน้อย 3 ครั้งคุณจริงสามารถบันทึกไบต์ด้วยการเปลี่ยนกลุ่มก่อนหน้านี้เป็นกลุ่มที่ไม่ได้จับภาพดังกล่าวว่าทุกคน\10ที่กลายเป็น\9s (ใช้กลอุบายที่คล้ายกันหากคุณใช้กลุ่ม11อย่างน้อย 5 ครั้งเป็นต้น)


ทำไม 11 ต้อง 5 ครั้งจึงจะคุ้มเมื่อ 10 ต้อง 3
Nic Hartley

1
@QPaysTaxes สามารถใช้$9แทน$10หรือ$11บันทึกครั้งเดียวได้หนึ่งไบต์ การเปลี่ยน$10เป็น$9ต้องใช้หนึ่ง?:ซึ่งคือสองไบต์ดังนั้นคุณจะต้องสาม$10s เพื่อบันทึกบางสิ่งบางอย่าง การเปลี่ยน$11เป็น$9ต้องใช้สอง?:s ซึ่งเป็นสี่ไบต์ดังนั้นคุณจะต้องห้า$11s เพื่อบันทึกบางสิ่ง (หรือห้า$10และ$11รวมกัน)
Martin Ender

10

การเรียกซ้ำสำหรับการใช้รูปแบบซ้ำ

กำมือหนึ่งรสชาติสนับสนุนการเรียกซ้ำ ( ตามความรู้ของฉัน Perl PCRE และ Ruby) แม้ว่าคุณจะไม่ได้พยายามแก้ปัญหาแบบเรียกซ้ำคุณสมบัตินี้สามารถบันทึกจำนวนมากในรูปแบบที่ซับซ้อนมากขึ้น ไม่จำเป็นต้องทำการโทรไปยังกลุ่มอื่น (ชื่อหรือหมายเลข) ภายในกลุ่มนั้น หากคุณมีรูปแบบบางอย่างที่ปรากฏหลาย ๆ ครั้งใน regex ของคุณเพียงแค่จัดกลุ่มและอ้างอิงถึงนอกกลุ่มนั้น สิ่งนี้ไม่แตกต่างจากการเรียกรูทีนย่อยในภาษาการเขียนโปรแกรมปกติ ดังนั้นแทนที่จะ

...someComplexPatternHere...someComplexPatternHere...someComplexPatternHere... 

ใน Perl / PCRE คุณสามารถทำได้:

...(someComplexPatternHere)...(?1)...(?1)...

หรือในทับทิม:

...(someComplexPatternHere)...\g<1>...\g<1>...

หากเป็นกลุ่มแรก (แน่นอนว่าคุณสามารถใช้หมายเลขใดก็ได้ในการโทรซ้ำ)

โปรดทราบว่านี่ไม่เหมือนกับ backreference ( \1) การอ้างอิงกลับจับคู่สตริงเดียวกันกับที่กลุ่มจับคู่ไว้ในครั้งที่แล้ว การเรียกรูทีนย่อยเหล่านี้จะประเมินรูปแบบอีกครั้ง เป็นตัวอย่างสำหรับการsomeComplexPatternHereใช้คลาสตัวละครที่มีความยาว:

a[0_B!$]b[0_B!$]c[0_B!$]d

สิ่งนี้จะตรงกับสิ่งที่ต้องการ

aBb0c!d

โปรดทราบว่าคุณไม่สามารถใช้การอ้างอิงย้อนกลับที่นี่ในขณะที่คงพฤติกรรมไว้ได้ backreference จะล้มเหลวในสตริงดังกล่าวเนื่องจากBและ0และ!จะไม่เหมือนกัน อย่างไรก็ตามด้วยการเรียกรูทีนย่อยรูปแบบจะถูกประเมินค่าใหม่จริง ๆ รูปแบบข้างต้นเทียบเท่ากับ

a([0_B!$])b(?1)c(?1)d

การจับภาพในการเรียกรูทีนย่อย

ข้อควรระวังหนึ่งประการสำหรับ Perl และ PCRE: หากกลุ่ม1ในตัวอย่างข้างต้นมีกลุ่มเพิ่มเติมการเรียกรูทีนย่อยจะไม่จดจำการดักจับ ลองพิจารณาตัวอย่างนี้:

(\w(\d):)\2 (?1)\2 (?1)\2

สิ่งนี้จะไม่ตรงกัน

x1:1 y2:2 z3:3

เนื่องจากหลังจากการเรียกรูทีนย่อยกลับมาการดักจับกลุ่มใหม่2จะถูกยกเลิก รูปแบบนี้จะตรงกับสตริงนี้แทน:

x1:1 y2:1 z3:1

ซึ่งแตกต่างจากทับทิมที่โทร subroutine ทำรักษาจับของพวกเขาดังนั้นเทียบเท่าทับทิม regex (\w(\d):)\2 \g<1>\2 \g<1>\2จะตรงกับครั้งแรกของตัวอย่างข้างต้น


คุณสามารถใช้\1สำหรับ Javascript และ PHP ด้วย (ฉันเดา)
Ismael Miguel

5
@IsmaelMiguel นี่ไม่ใช่การอ้างอิงย้อนกลับ สิ่งนี้จะประเมินรูปแบบอีกครั้ง เช่น(..)\1จะจับคู่ababแต่ล้มเหลวในabbaขณะที่(..)(?1)จะจับคู่หลัง จริงๆแล้วมันเป็นการเรียกรูทีนย่อยในแง่ที่ว่านิพจน์นั้นถูกนำไปใช้อีกครั้งแทนที่จะจับคู่สิ่งที่ตรงกับครั้งที่แล้ว
Martin Ender

ว้าวฉันไม่รู้เลย! เรียนรู้สิ่งใหม่ทุกวัน
Ismael Miguel

ใน. NET (หรือรสชาติอื่น ๆ ที่ไม่มีคุณสมบัตินี้):(?=a.b.c)(.[0_B!$]){3}d
jimmy23013

@ user23013 ที่ดูเหมือนเฉพาะตัวอย่างนี้มาก ฉันไม่แน่ใจว่าสามารถใช้งานได้หรือไม่ถ้าฉันใช้ subpattern บางอันซ้ำใน lookarounds ต่างๆ
Martin Ender

9

ทำให้การแข่งขันล้มเหลว

เมื่อใช้ regex เพื่อแก้ปัญหาการคำนวณหรือจับคู่ภาษาที่ไม่ธรรมดาอย่างมากบางครั้งก็จำเป็นต้องทำให้สาขาของรูปแบบล้มเหลวโดยไม่คำนึงถึงตำแหน่งที่คุณอยู่ในสตริง วิธีการไร้เดียงสาคือการใช้ lookahead เชิงลบที่ว่างเปล่า:

(?!)

เนื้อหา (รูปแบบที่ว่างเปล่า) จับคู่เสมอดังนั้น lookahead เชิงลบจึงล้มเหลวเสมอ แต่บ่อยกว่านั้นมีตัวเลือกที่ง่ายกว่ามากเพียงใช้ตัวอักษรที่คุณรู้ว่าจะไม่ปรากฏในอินพุต เช่นถ้าคุณรู้ว่าข้อมูลของคุณจะประกอบด้วยตัวเลขเท่านั้นคุณสามารถใช้

!

หรืออักขระที่ไม่ใช่ตัวเลขอื่น ๆ ไม่ใช่เมตาเพื่อทำให้เกิดความล้มเหลว

แม้ว่าการป้อนข้อมูลของคุณอาจจะมีสตริงใด ๆ (?!)มีวิธีที่สั้นกว่า รสชาติใด ๆ ที่อนุญาตให้แองเคอร์ปรากฏภายในรูปแบบซึ่งตรงข้ามกับส่วนท้ายสามารถใช้โซลูชัน 2 ตัวอักษรต่อไปนี้:

a^
$a

อย่างไรก็ตามโปรดทราบว่ารสชาติบางอย่างจะรักษา^และ$เป็นตัวอักษรในตำแหน่งเหล่านี้เพราะเห็นได้ชัดว่าไม่ได้ทำให้รู้สึกว่าเป็นเบรก

ในรสชาติ ECMAScript ยังมีโซลูชัน 2 ตัวอักษรที่ค่อนข้างหรูหรา

[]

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


8

เพิ่มประสิทธิภาพคุณหรือของ

เมื่อใดก็ตามที่คุณมี 3 ทางเลือกใน RegEx ของคุณ:

/aliceblue|antiquewhite|aquamarine|azure/

ตรวจสอบว่ามีจุดเริ่มต้นทั่วไปไหม:

/a(liceblue|ntiquewhite|quamarine|zure)/

และอาจถึงจุดจบทั่วไปด้วย?

/a(liceblu|ntiquewhit|quamarin|zur)e/

หมายเหตุ: 3 เป็นเพียงการเริ่มต้นและจะพิจารณาความยาวเท่ากัน 4+ จะสร้างความแตกต่าง


แต่ถ้าไม่ใช่ทั้งหมดมีคำนำหน้าเหมือนกัน? (เพิ่มช่องว่างเพื่อความชัดเจนเท่านั้น)

/aliceblue|antiquewhite|aqua|aquamarine|azure
|beige|bisque|black|blanchedalmond|blue|blueviolet|brown|burlywood
|cadetblue|chartreuse|chocolate|coral|cornflowerblue|cornsilk|crimson|cyan/

จัดกลุ่มพวกเขาตราบเท่าที่กฎ 3+ มีเหตุผล:

/a(liceblue|ntiquewhite|qua|quamarine|zure)
|b(eige|isque|lack|lanchedalmond|lue|lueviolet|rown|urlywood)
|c(adetblue|hartreuse|hocolate|oral|ornflowerblue|ornsilk|rimson|yan)/

หรือแม้กระทั่งพูดคุยทั่วไปถ้าเอนโทรปีของคุณตรงกับ Usecase:

/\w(liceblue|ntiquewhite|qua|quamarine|zure
|eige|isque|lack|lanchedalmond|lue|lueviolet|rown|urlywood
|adetblue|hartreuse|hocolate|oral|ornflowerblue|ornsilk|rimson|yan)/

^ ในกรณีนี้เรามั่นใจว่าเราไม่ได้รับสิ่งใดclueหรือcrown slack Ryan

นี้"ตามการทดสอบบางอย่าง"ยังช่วยเพิ่มประสิทธิภาพการทำงานในขณะที่มันยังมีจุดยึดที่จะเริ่มต้นที่


1
หากจุดเริ่มต้นหรือจุดสิ้นสุดที่พบโดยทั่วไปมีความยาวมากกว่าหนึ่งอักขระแม้แต่การจัดกลุ่มสองสามารถสร้างความแตกต่าง ชอบaqua|aquamarine→ หรือaqua(|marine) aqua(marine)?
Paŭlo Ebermann

6

อันนี้ค่อนข้างง่าย แต่ก็คุ้มค่าที่จะระบุ:

หากคุณพบว่าตัวเองทำซ้ำตัวอักษรชั้นเรียน[a-zA-Z]คุณสามารถอาจเพียงแค่ใช้[a-z]และผนวกi(CASE- ฉัน nsensitive ปรับปรุง) เพื่อ regex ของคุณ

ตัวอย่างเช่นใน Ruby ทั้งสอง regexes ต่อไปนี้เทียบเท่า:

/[a-zA-Z]+\d{3}[a-zA-Z]+/
/[a-z]+\d{3}[a-z]/i - สั้นลง 7 ไบต์

สำหรับเรื่องนั้นโมดิฟายเออร์ตัวอื่นสามารถลดความยาวทั้งหมดของคุณได้เช่นกัน แทนที่จะทำสิ่งนี้:

/(.|\n)/

ซึ่งตรงกับอักขระใด ๆ (เนื่องจากจุดไม่ตรงกับบรรทัดใหม่) ให้ใช้ตัวปรับs ingle-line sซึ่งทำให้การจับคู่จุดขึ้นบรรทัดใหม่

/./s - สั้นลง 3 ไบต์


ใน Ruby มีคลาสของตัวละครมากมายสำหรับ regex ดูหน้านี้และค้นหา "คุณสมบัติของตัวละคร"
ตัวอย่างที่ดีคือ "สัญลักษณ์สกุลเงิน" ตามวิกิพีเดียมีสัญลักษณ์สกุลเงินมากมายที่เป็นไปได้และการใส่ไว้ในคลาสของตัวละครจะมีราคาแพงมาก ( [$฿¢₡Ð₫€.....]) ในขณะที่คุณสามารถจับคู่ใด ๆ ของพวกเขาใน 6 ไบต์:\p{Sc}


1
ยกเว้น JavaScript ซึ่งsไม่รองรับตัวดัดแปลง :( แต่คุณสามารถใช้/[^]/เคล็ดลับที่เป็นกรรมสิทธิ์ของ JavaScript ได้ที่นั่น
จัดทำ

โปรดทราบว่า(.|\n)ไม่สามารถใช้งานได้ในบางรสชาติเพราะ.มักจะไม่ตรงกับตัวแยกบรรทัดประเภทอื่น อย่างไรก็ตามวิธีธรรมเนียมที่จะต้องทำเช่นนี้ (ไม่s) เป็นซึ่งเป็นไบต์เดียวกับ[\s\S] (.|\n)
Martin Ender

@ MartinBüttnerความคิดของฉันคือการเก็บมันไว้พร้อมกับเคล็ดลับที่เกี่ยวข้องกับการสิ้นสุดบรรทัดอื่น ๆ แต่ถ้าคุณรู้สึกว่าคำตอบนี้เป็นเรื่องเกี่ยวกับการดัดแปลงมากขึ้นฉันก็ไม่คัดค้านถ้าคุณโพสต์ใหม่
จัดการ

@ manatwork เสร็จแล้ว (และเพิ่มเคล็ดลับเฉพาะที่ไม่ใช่ ES ที่เกี่ยวข้องเช่นกัน)
Martin Ender

6

เครื่องมือแยกวิเคราะห์ภาษาที่เรียบง่าย

คุณสามารถสร้าง parser \d+|\w+|".*?"|\n|\Sง่ายมากกับเรื่องเช่น โทเค็นที่คุณต้องการจับคู่จะถูกคั่นด้วยอักขระ RE 'หรือ'

ทุกครั้งที่ RE engine พยายามจับคู่ที่ตำแหน่งปัจจุบันในข้อความมันจะลองใช้รูปแบบแรกจากนั้นเลือกที่สองเป็นต้นหากล้มเหลว (เช่นอักขระเว้นวรรคที่นี่) มันจะย้ายและลองจับคู่อีกครั้ง . คำสั่งซื้อเป็นสิ่งสำคัญ หากเราวาง\Sคำก่อนหน้า\d+คำนั้น\Sจะจับคู่แรกกับตัวละครที่ไม่ใช่ช่องว่างใด ๆ ที่จะทำลาย parser ของเรา

ตัว".*?"จับสตริงใช้ตัวดัดแปลงที่ไม่ใช่โลภดังนั้นเราจึงจับคู่ทีละสตริงเท่านั้น หาก RE ของคุณไม่มีฟังก์ชันที่ไม่โลภคุณสามารถใช้ฟังก์ชัน "[^"]*"ที่เทียบเท่าได้

ตัวอย่าง Python:

text = 'd="dogfinder"\nx=sum(ord(c)*872 for c in "fish"+d[3:])'
pat = r'\d+|\w+|".*?"|\n|\S'
print re.findall(pat, text)

['d', '=', '"dogfinder"', '\n', 'x', '=', 'sum', '(', 'ord', '(', 'c', ')',
    '*', '872', 'for', 'c', 'in', '"fish"', '+', 'd', '[', '3', ':', ']', ')']

ตัวอย่าง Golfed Python:

# assume we have language text in A, and a token processing function P
map(P,findall(r'\d+|\w+|".*?"|\n|\S',A))

คุณสามารถปรับรูปแบบและลำดับของภาษาที่คุณต้องการให้ตรงกัน เทคนิคนี้ใช้ได้ดีสำหรับ JSON, HTML พื้นฐานและนิพจน์ตัวเลข มันถูกใช้อย่างประสบความสำเร็จหลายครั้งกับ Python 2 แต่ควรเป็นแบบทั่วไปพอที่จะทำงานในสภาพแวดล้อมอื่น ๆ


6

\K แทนที่จะมองในแง่ดี

PCRE และ Perl รองรับลำดับ escape \Kซึ่งจะรีเซ็ตจุดเริ่มต้นของการแข่งขัน นั่นคือab\Kcdจะต้องมีสายเข้าของคุณมีแต่การแข่งขันรายงานจะเป็นabcdcd

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

(?<=abc)def
abc\Kdef

สิ่งนี้เทียบเท่ากับวัตถุประสงค์ส่วนใหญ่แต่ไม่ทั้งหมด ความแตกต่างนำทั้งข้อดีและข้อเสียมาด้วย:

  • Upside: PCRE และ Perl ไม่สนับสนุน lookbehinds ความยาวโดยพลการ (เฉพาะ. NET ทำ) (?<=ab*)นั่นคือคุณไม่สามารถทำสิ่งที่ชอบ แต่ด้วย\Kคุณสามารถใส่รูปแบบใด ๆ ไว้ข้างหน้า! ได้ab*\Kผล นี่ทำให้เทคนิคนี้มีประสิทธิภาพยิ่งขึ้นอย่างมากในกรณีที่สามารถใช้งานได้
  • Upside: Lookarounds ไม่ย้อนรอย สิ่งนี้มีความเกี่ยวข้องหากคุณต้องการจับภาพบางสิ่งในหน้าตาเพื่อย้อนกลับในภายหลัง แต่มีการบันทึกที่เป็นไปได้หลายประการซึ่งทั้งหมดนำไปสู่การจับคู่ที่ถูกต้อง ในกรณีนี้เอ็นจิ้น regex จะลองหนึ่งในความเป็นไปได้เหล่านั้นเท่านั้น เมื่อใช้\Kส่วนหนึ่งของ regex นั้นกำลังย้อนรอยเหมือนทุกสิ่งทุกอย่าง
  • ข้อเสีย: อย่างที่คุณอาจจะรู้ว่าการแข่งขันหลายรายการของ regex ไม่สามารถซ้อนทับกันได้ บ่อยครั้งที่ lookarounds ถูกใช้เพื่อแก้ไขข้อ จำกัด นี้บางส่วนเนื่องจาก lookahead สามารถตรวจสอบความถูกต้องของส่วนของสตริงที่ถูกใช้ไปแล้วโดยการจับคู่ก่อนหน้า ดังนั้นถ้าคุณต้องการที่จะตรงกับตัวละครทุกตัวที่ตามมา คุณอาจใช้ab (?<=ab).รับอินพุต

    ababc
    

    นี้จะตรงกับที่สองและa cสิ่งนี้ไม่สามารถทำซ้ำ\Kได้ หากคุณใช้ab\K.คุณจะได้รับนัดแรกเท่านั้นเพราะตอนนี้abไม่ได้อยู่ในการค้นหา


หากรูปแบบใช้\Kลำดับการยกเว้นภายในการยืนยันที่เป็นบวกการเริ่มต้นการแข่งขันที่ประสบความสำเร็จที่รายงานไว้อาจมากกว่าการสิ้นสุดของการแข่งขัน
hwnd

@hwnd จุดของฉันจะได้รับว่าababcมีวิธีการเพื่อให้ตรงกับทั้งสองไม่มีaและมีc \Kคุณจะได้รับเพียงหนึ่งการแข่งขัน
Martin Ender

คุณถูกต้องไม่ใช่ด้วยคุณสมบัติ คุณจะต้องยึดเหนี่ยวกับ\G
hwnd

@ hwnd Ah ฉันเห็นจุดของคุณแล้ว แต่ผมคิดว่าที่จุดนั้น (จากมุมมองของการเล่นกอล์ฟ) คุณก็ยังดีที่มี lookbehind ลบ becaue จริงแม้คุณอาจจำเป็นต้องใช้มันอยู่แล้วตั้งแต่คุณไม่สามารถมั่นใจได้ว่าจากการแข่งขันที่ผ่านมาเป็นจริง. a
Martin Ender

1
การใช้ที่น่าสนใจของ\ K =)
hwnd

5

จับคู่ตัวละครใด ๆ

รสชาติ ECMAScript ไม่มีsตัวดัดแปลงที่ทำให้.ตรงกับตัวละครใด ๆ (รวมถึงการขึ้นบรรทัดใหม่) ซึ่งหมายความว่าไม่มีวิธีแก้ปัญหาอักขระเดี่ยวที่จะจับคู่อักขระที่ไม่มีตัวตนโดยสมบูรณ์ สารละลายมาตรฐานในรสชาติอื่น ๆ (เมื่อไม่ต้องการที่จะใช้sด้วยเหตุผลบางอย่าง) [\s\S]เป็น อย่างไรก็ตาม ECMAScript เป็นรสชาติเท่านั้น (เพื่อความรู้ของฉัน) [^]ที่สนับสนุนการเรียนตัวอักษรที่ว่างเปล่าและด้วยเหตุนี้มีทางเลือกที่สั้นมาก: นี่คือคลาสอักขระว่างเมื่อตะกี้ ​​- นั่นคือมันตรงกับตัวละครใด ๆ

แม้สำหรับรสชาติอื่น ๆ เราสามารถเรียนรู้จากเทคนิคนี้: หากเราไม่ต้องการใช้s(เช่นเพราะเรายังต้องการความหมายตามปกติ.ในที่อื่น ๆ ) ยังคงมีวิธีที่สั้นกว่าที่จะจับคู่อักขระขึ้นบรรทัดใหม่และพิมพ์ได้ หากมีอักขระบางตัวที่เรารู้ว่าไม่ปรากฏในอินพุต สมมติว่าเรากำลังประมวลผลตัวเลขที่คั่นด้วยการขึ้นบรรทัดใหม่ จากนั้นเราสามารถจับคู่อักขระใด ๆ[^!]ได้เนื่องจากเรารู้ว่า!จะไม่ได้เป็นส่วนหนึ่งของสตริง นี้ช่วยประหยัดไบต์ที่สองในช่วงที่ไร้เดียงสาหรือ[\s\S][\d\n]


4
ใน Perl \Nหมายถึงสิ่งที่มีความ.หมายนอก/sโหมดยกเว้นว่าจะไม่ได้รับผลกระทบจากโหมด
Konrad Borowski

4

ใช้กลุ่มอะตอมและปริมาณที่เป็นเจ้าของ

ผมพบว่ากลุ่มอะตอม ( (?>...)) และปริมาณหวง ( ?+, *+, ++, {m,n}+) บางครั้งมีประโยชน์มากสำหรับการเล่นกอล์ฟ มันตรงกับสตริงและไม่อนุญาตการย้อนรอยในภายหลัง ดังนั้นมันจะจับคู่สตริงที่จับคู่แรกเท่านั้นที่พบโดยเอ็นจิน regex

ตัวอย่างเช่น: ในการจับคู่สตริงที่มีจำนวนคี่a's ที่จุดเริ่มต้นซึ่งไม่ได้ตามมาด้วยaคุณสามารถใช้

^(aa)*+a
^(?>(aa)*)a

สิ่งนี้ช่วยให้คุณใช้สิ่งต่าง ๆ.*ได้อย่างอิสระและหากมีการจับคู่ที่ชัดเจนจะไม่มีความเป็นไปได้ที่จะจับคู่อักขระมากหรือน้อยเกินไปซึ่งอาจทำให้รูปแบบของคุณแตก

ใน. NET regex (ซึ่งไม่มีปริมาณที่เป็นเจ้าของ) คุณสามารถใช้สิ่งนี้กับกลุ่มป๊อป 1 ตัวคูณที่ยิ่งใหญ่ที่สุดของ 3 (สูงสุด 30) ครั้ง (ไม่เล่นกอล์ฟได้ดีมาก):

(?>((?<-1>){3}|){10})

1
ECMAscript ก็หายไปปริมาณที่เป็นเจ้าของหรือกลุ่มอะตอม :(
CSᵠ

4

ลืมกลุ่มที่ถูกจับหลังจากนิพจน์ย่อย (PCRE)

สำหรับ regex นี้:

^((a)(?=\2))(?!\2)

หากคุณต้องการล้าง \ 2 หลังกลุ่ม 1 คุณสามารถใช้การสอบถามซ้ำได้:

^((a)(?=\2)){0}(?1)(?!\2)

มันจะจับคู่aaในขณะที่ก่อนหน้านี้จะไม่ บางครั้งคุณยังสามารถใช้??หรือแม้กระทั่งในสถานที่ของ?{0}

สิ่งนี้อาจมีประโยชน์หากคุณใช้การเรียกซ้ำหลายครั้งและกลุ่ม backreferences หรือกลุ่มเงื่อนไขบางกลุ่มปรากฏในที่ต่าง ๆ ใน regex ของคุณ

นอกจากนี้โปรดทราบว่ากลุ่มอะตอมจะถูกใช้เพื่อเรียกซ้ำใน PCRE ดังนั้นสิ่งนี้จะไม่ตรงกับตัวอักษรเดียวa:

^(a?){0}(?1)a

ฉันยังไม่ได้ลองในรสชาติอื่น

สำหรับ lookaheads คุณสามารถใช้ฟิล์มเนกาทีฟสองเท่าเพื่อจุดประสงค์นี้:

^(?!(?!(a)(?=\1))).(?!\1)

4

นิพจน์ทางเลือก

บางครั้งมันก็มีประโยชน์ที่จะจำ

(abc)?

เป็นส่วนใหญ่เช่นเดียวกับ

(abc|)

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

((abc)?)

เวอร์ชันที่ใช้|ยังมีประโยชน์เมื่อคุณต้องการห่อนิพจน์ในรูปแบบอื่นของกลุ่มอยู่แล้วและไม่สนใจเกี่ยวกับการจับภาพ:

(?=(abc)?)
(?=abc|)

(?>(abc)?)
(?>abc|)

ในที่สุดเคล็ดลับนี้ยังสามารถนำไปใช้กับ ungreedy ?ที่จะช่วยประหยัดไบต์แม้ในรูปแบบดิบ (และดังนั้น 3 ไบต์เมื่อรวมกับกลุ่มรูปแบบอื่น ๆ ):

(abc)??
(|abc)

1

Lookaheads หลายรายการที่จับคู่ (. NET)

หากคุณมีการสร้าง lookahead 3 ครั้งขึ้นไปที่จับคู่เสมอ (เพื่อจับภาพนิพจน์ย่อย) หรือมีตัวระบุปริมาณบน lookahead ตามด้วยอย่างอื่นดังนั้นจึงควรอยู่ในกลุ่มที่ไม่ถูกจับ:

(?=a)(?=b)(?=c)
((?=a)b){...}

สิ่งเหล่านี้สั้นกว่า:

(?(?(?(a)b)c))
(?(a)b){...}

โดยที่aไม่ควรเป็นชื่อของกลุ่มที่ถูกจับ คุณไม่สามารถใช้|หมายถึงสิ่งปกติในbและcโดยไม่ต้องเพิ่มวงเล็บอีกคู่

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

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.