Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Ruby

เชี่ยวชาญ Ruby 2.0 Onigmo: ใช้ Conditional Expressions ใน Regular Expressions

ใน features 多いที่ Ruby 2.0 ส่งมอบ 2013 อันที่ผมสนใจน้อยสุดคือ regex engine ใหม่ Onigmo regex คือ regex ทำไมสนใจ Ruby implement ยังไง

แต่ Onigmo regex engine มี tricks น่าสนใจ รวมถึง conditional expressions ภายใน regex

โพสต์นี้ดำดิ่ง regex conditionals เรียนรู้ quirks ของ Ruby implementation และ tricks workaround limitations เริ่มเลย!

Groups & Capturing

เข้าใจ conditionals ใน regex ต้องเข้าใจ grouping และ capturing ก่อน

จินตนาการ list US cities:


Fayetteville, AR
Seattle, WA

อยากแยก city name จาก state abbreviation วิธีหนึ่ง multiple matches:


PLACE = "Fayetteville, AR"
# City: Match char ไม่ใช่ comma
PLACE.match(/[^,]+/) # => #
# Separator: Match comma และ optional spaces
PLACE.match(/, */) # => #
# State: Match 2-letter code ท้าย string
PLACE.match(/[A-Z]{2}$/) # => #

Work แต่ verbose ด้วย groups capture ทั้ง city และ state ด้วย regex เดียว

รวม regex ข้างบน ล้อมแต่ละส่วนด้วย parentheses Parens คือ grouping ใน regex:


PLACE = "Fayetteville, AR"
m = PLACE.match(/([^,]+)(, *)([A-Z]{2})/)
# => #

เห็น capture ทั้ง city และ state เข้าถึงด้วยการ treat MatchData 像 array:


m[1] # => "Fayetteville"
m[3] # => "AR"

ปัญหา grouping ข้างบน: captured data ใน array ถ้า position เปลี่ยน ต้อง update code หรือ bug

เช่น decide capturing ", " silly ลบ parens ส่วนนั้น:


m = PLACE.match(/([^,]+), *([A-Z]{2})/)
# => #
m[3] # => nil

ตอนนี้ m[3] ไม่มี state - bug city

Named Groups

ทำ regex groups semantic มากขึ้นด้วยการ命名 syntax ใกล้เคียง ใส่ parens ระบุ name:


/(?regex)/

Apply กับ city/state regex:


m = PLACE.match(/(?[^,]+), *(?[A-Z]{2})/)
# => #

เข้าถึง captured data treat MatchData 像 hash:


m[:city] # => "Fayetteville"

Conditionals

Conditionals ใน regex รูปแบบ /(?(A)X|Y)/ วิธีใช้ valid:


# ถ้า A true evaluate X ไม่然 Y
/(?(A)X|Y)/
# ถ้า A true แล้ว X
/(?(A)X)/
# ถ้า A false แล้ว Y
/(?(A)|Y)/

Two common options สำหรับ condition A:

  • Named/numbered group captured ไหม?
  • Look-around evaluate true ไหม?

มาดูใช้งาน:

Group captured ไหม?

Check presence of group 用 ?(n) syntax n คือ integer หรือ group name ล้อม <> หรือ ''


# Group number 1 captured ไหม?
/(?(1)foo|bar)/
# Group 名 "mygroup" captured ไหม?
/(?()foo|bar)/

Example

จินตนาการ parse US telephone numbers มี three-digit area code optional เว้นแต่ number เริ่มด้วย 1


1-800-555-1212 # Valid
800-555-1212 # Valid
555-1212 # Valid
1-555-1212 # INVALID!!

用 conditional ทำ area code required ก็ต่อเมื่อเริ่มด้วย 1:


# Regex complex แต่ทำจาก simple pieces
# `^(1-)?` string เริ่ม "1-" ไหม ถ้า capture as group 1
# `(?(1)` Group one 有 capture ไหม?
# `\d{3}-` ถ้ามี required match 三位數和 dash (area code)
# `|(\d{3}-)?` ถ้าไม่มี optional match 三位數和 dash (area code)
# `\d{3}-\d{4}` match เหลือ phone number ที่ always required
re = /^(1-)?(?(1)\d{3}-|(\d{3}-)?)\d{3}-\d{4}/

"1-800-555-1212".match(re) #=> #
"800-555-1212".match(re) #=> #
"555-1212".match(re) #=> #
"1-555-1212".match(re) #=> nil

Limitations

ปัญหา group-based conditionals: matching group "consume" characters ใน string characters นั้นไม่สามารถ用 conditional ได้

เช่น code พยายาม match 100 ถ้า "USD" present:


"100USD".match(/(USD)(?(1)\d+)/) # nil

Perl และภาษาอื่นเพิ่ม look-ahead ใน conditional ให้ trigger conditional จาก text 任何位置 Ruby ไม่有 workaround creative

Look-around

โชคดี workaround limitations ใน Ruby regex conditionals ด้วยการ abuse look-around expressions

Look-around คืออะไร?

ปกติ regex parser step through string เริ่มถึงสิ้นสุดหา matches เหมือน move cursor ซ้ายขวาใน word processor

Look-ahead และ look-behind expressions ทำงานต่างออกไป inspect string 不消耗 characters 完了 cursor อยู่เดิม

Intro look-arounds ดีๆ: Rexegg guide to mastering look ahead and look behind

Syntax:

TypeSyntaxExample
Look Ahead(?=query)\d+(?= dollars) match 100 ใน "100 dollars"
Negative Look Ahead(?!query)\d+(?! dollars) match 100 ถ้า NOT followed by "dollars"
Look Behind(?<=query)(?<=lucky )\d match 7 ใน "lucky 7"
Negative Look Behind(?(? match 7 在 "lucky 7"

Abusing look-arounds 增强 conditionals

ใน conditional query ได้แค่ groups ที่ set แล้ว ปกติ group content consumed ไม่ available ให้ conditional

แต่用 look-ahead set group 不消耗 characters! mind blown?

จำ code ที่ไม่ work?


"100USD".match(/(USD)(?(1)\d+)/) # nil

修改 capture group ใน look-ahead ทำงานได้:


"100USD".match(/(?=.*(USD))(?(1)\d+)/)
# => #

แยก query ดู:

  • (?=.*(USD)) 用 look-ahead scan text หา "USD" capture group 1
  • (?(1) ถ้า group 1 มีอยู่
  • \d+ แล้ว match หนึ่งหรือ多個 numbers

น่าสนใจไหม?