การจับคู่กลุ่ม Ruby Regexp กำหนดตัวแปรใน 1 บรรทัด


125

ฉันกำลังพยายาม rexp สตริงเป็นตัวแปรหลายตัว สตริงตัวอย่าง:

ryan_string = "RyanOnRails: This is a test"

ฉันจับคู่กับ regexp นี้โดยมี 3 กลุ่ม:

ryan_group = ryan_string.scan(/(^.*)(:)(.*)/i)

ตอนนี้ในการเข้าถึงแต่ละกลุ่มฉันต้องทำสิ่งนี้:

ryan_group[0][0] (first group) RyanOnRails
ryan_group[0][1] (second group) :
ryan_group[0][2] (third group) This is a test

ดูเหมือนจะไร้สาระและรู้สึกว่าฉันทำอะไรผิด ฉันคาดหวังว่าจะสามารถทำสิ่งนี้ได้:

g1, g2, g3 = ryan_string.scan(/(^.*)(:)(.*)/i)

เป็นไปได้หรือไม่ หรือมีวิธีที่ดีกว่าที่ฉันกำลังทำอยู่?

คำตอบ:


199

คุณไม่ต้องการ scanสิ่งนี้เพราะมันไม่ค่อยสมเหตุสมผล คุณสามารถใช้String#matchซึ่งจะส่งคืนMatchDataวัตถุจากนั้นคุณสามารถเรียก#capturesคืน Array of captures ได้ สิ่งนี้:

#!/usr/bin/env ruby

string = "RyanOnRails: This is a test"
one, two, three = string.match(/(^.*)(:)(.*)/i).captures

p one   #=> "RyanOnRails"
p two   #=> ":"
p three #=> " This is a test"

โปรดทราบว่าหากไม่พบรายการที่ตรงกันString#matchจะคืนค่าศูนย์ดังนั้นสิ่งนี้อาจได้ผลดีกว่า:

if match = string.match(/(^.*)(:)(.*)/i)
  one, two, three = match.captures
end

แม้ว่าscanจะไม่ค่อยสมเหตุสมผลสำหรับเรื่องนี้ มันยังคงทำงานคุณเพียงแค่ต้องแบน Array ที่ส่งคืนก่อนone, two, three = string.scan(/(^.*)(:)(.*)/i).flatten


6
ระวังว่าหากไม่พบการจับคู่การจับคู่จะคืนค่าศูนย์และคุณจะได้รับ NilError หากคุณอยู่ใน Rails ฉันขอแนะนำให้คุณเปลี่ยน: one, two, three = string.match(/(^.*)(:)(.*)/i).captures เป็น: one, two, three = string.match(/(^.*)(:)(.*)/i).try(:captures)
Andrea Salicetti

5
@AndreaSalicetti ฉันแก้ไขโพสต์ของฉันแล้วฉันไม่ได้เพิ่มรหัสเฉพาะ Rails ลงไปดังนั้นฉันจึงได้แก้ไขด้วยเวอร์ชันสำหรับจัดการวัตถุที่ไม่มีการส่งคืน
Lee Jarvis

3
คุณยังสามารถให้&.โอเปอเรเตอร์ใหม่นำกลับมาใช้งานในบรรทัดและแม้กระทั่งใช้สองครั้งเมื่อมีกลุ่มการบันทึกเพียงกลุ่มเดียว เช่น .. ,string.match(regex)&.captures&.first
Gerry Shaw

46

คุณสามารถใช้Matchหรือ = ~ แทนซึ่งจะให้การจับคู่เดียวและคุณสามารถเข้าถึงข้อมูลการจับคู่ด้วยวิธีเดียวกันหรือใช้ตัวแปรการจับคู่พิเศษ $ 1, $ 2, $ 3

สิ่งที่ต้องการ:

if ryan_string =~ /(^.*)(:)(.*)/i
   first = $1
   third = $3
end

5
@Gaston นั่นคือไวยากรณ์ regexp ดั้งเดิมที่มาจาก Perl :)
ohaleck

28

คุณสามารถตั้งชื่อการแข่งขันที่จับได้

string = "RyanOnRails: This is a test"
/(?<one>^.*)(?<two>:)(?<three>.*)/i =~ string
puts one, two, three

ไม่ได้ผลถ้าคุณย้อนกลับลำดับของสตริงและ regex


6

คุณต้องตัดสินใจว่าเป็นความคิดที่ดีหรือไม่ แต่ Ruby regexp สามารถ (โดยอัตโนมัติ) กำหนดตัวแปรท้องถิ่นได้ให้คุณได้!

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

ryan_string = "RyanOnRails: This is a test"
/^(?<webframework>.*)(?<colon>:)(?<rest>)/ =~ ryan_string
# This defined three variables for you. Crazy, but true.
webframework # => "RyanOnRails"
puts "W: #{webframework} , C: #{colon}, R: #{rest}"

(ดูที่http://ruby-doc.org/core-2.1.1/Regexp.htmlค้นหา "local variable")

หมายเหตุ: ตามที่ระบุไว้ในความคิดเห็นฉันเห็นว่ามีคำตอบที่คล้ายกันและก่อนหน้านี้สำหรับคำถามนี้โดย @toonsend ( https://stackoverflow.com/a/21412455 ) ฉันไม่คิดว่าฉันกำลัง "ขโมย" แต่ถ้าคุณต้องการความยุติธรรมด้วยการยกย่องและให้เกียรติคำตอบแรกอย่าลังเล :) ฉันหวังว่าจะไม่มีสัตว์ใดได้รับอันตราย


คำตอบนี้ดูคล้ายกับstackoverflow.com/a/21412455/525478ซึ่งเก่ากว่าหนึ่งปี ...
Brad Werth

@BradWerth ฉันเดาว่าฉันไม่เห็นสิ่งนั้น แต่ฉันอัปเดตคำตอบเพื่อรวมข้อกังวลของคุณ
เฟลิกซ์

5

scan() จะพบการจับคู่ที่ไม่ทับซ้อนกันทั้งหมดของ regex ในสตริงของคุณดังนั้นแทนที่จะส่งคืนอาร์เรย์ของกลุ่มของคุณอย่างที่คุณคาดหวังไว้มันจะส่งคืนอาร์เรย์อาร์เรย์

คุณน่าจะดีกว่าเมื่อใช้match()แล้วรับอาร์เรย์ของการจับภาพโดยใช้MatchData#captures:

g1, g2, g3 = ryan_string.match(/(^.*)(:)(.*)/i).captures

อย่างไรก็ตามคุณสามารถทำได้scan()หากคุณต้องการ:

g1, g2, g3 = ryan_string.scan(/(^.*)(:)(.*)/i)[0]
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.