อาร์เรย์ของความเป็นไปได้: คู่มือการจับคู่รูปแบบทับทิม
เผยแพร่แล้ว: 2022-03-11การจับคู่รูปแบบเป็นคุณสมบัติใหม่ที่สำคัญใน Ruby 2.7 มันได้รับการผูกมัดกับลำตัวเพื่อให้ทุกคนที่สนใจสามารถติดตั้ง Ruby 2.7.0-dev และตรวจสอบได้ โปรดจำไว้ว่ายังไม่มีการสรุปผลใดๆ และทีมนักพัฒนากำลังมองหาข้อเสนอแนะ ดังนั้นหากคุณมี คุณสามารถแจ้งให้ผู้มอบหมายงานทราบก่อนที่คุณลักษณะนี้จะใช้งานได้จริง
ฉันหวังว่าคุณจะเข้าใจว่าการจับคู่รูปแบบคืออะไรและใช้งานอย่างไรใน Ruby หลังจากอ่านบทความนี้
การจับคู่รูปแบบคืออะไร?
การจับคู่รูปแบบเป็นคุณลักษณะที่พบได้ทั่วไปในภาษาโปรแกรมที่ใช้งานได้ ตามเอกสารของ Scala การจับคู่รูปแบบเป็น "กลไกในการตรวจสอบค่าเทียบกับรูปแบบ การจับคู่ที่ประสบความสำเร็จยังสามารถแยกแยะคุณค่าออกเป็นส่วนๆ ของมันได้”
อย่าสับสนกับ Regex การจับคู่สตริง หรือการรู้จำรูปแบบ การจับคู่รูปแบบไม่เกี่ยวข้องกับสตริง แต่โครงสร้างข้อมูลแทน ครั้งแรกที่ฉันพบการจับคู่รูปแบบเมื่อประมาณสองปีที่แล้วเมื่อฉันลองใช้น้ำอมฤต ฉันกำลังเรียนรู้ Elixir และพยายามแก้ปัญหาอัลกอริธึมด้วย ฉันเปรียบเทียบโซลูชันของฉันกับโซลูชันอื่นๆ และพบว่าพวกเขาใช้การจับคู่รูปแบบ ซึ่งทำให้โค้ดกระชับและอ่านง่ายขึ้นมาก
ด้วยเหตุนี้การจับคู่รูปแบบจึงสร้างความประทับใจให้ฉันจริงๆ นี่คือสิ่งที่การจับคู่รูปแบบใน Elixir ดูเหมือน:
[a, b, c] = [:hello, "world", 42] a #=> :hello b #=> "world" c #=> 42
ตัวอย่างข้างต้นดูเหมือนการ มอบหมายหลายรายการ ใน Ruby อย่างไรก็ตามมันเป็นมากกว่านั้น นอกจากนี้ยังตรวจสอบว่าค่าตรงกันหรือไม่:
[a, b, 42] = [:hello, "world", 42] a #=> :hello b #=> "world"
ในตัวอย่างข้างต้น ตัวเลข 42 ทางด้านซ้ายมือไม่ใช่ตัวแปรที่กำหนด เป็นค่าที่จะตรวจสอบว่าองค์ประกอบเดียวกันในดัชนีนั้น ๆ ตรงกับองค์ประกอบทางขวามือหรือไม่
[a, b, 88] = [:hello, "world", 42] ** (MatchError) no match of right hand side value
ในตัวอย่างนี้ แทนที่จะกำหนดค่า MatchError
จะถูกยกขึ้นแทน เนื่องจากหมายเลข 88 ไม่ตรงกับหมายเลข 42
มันยังใช้งานได้กับแผนที่ (ซึ่งคล้ายกับแฮชใน Ruby):
%{"name": "Zote", "title": title } = %{"name": "Zote", "title": "The mighty"} title #=> The mighty
ตัวอย่างข้างต้นตรวจสอบว่าค่าของ name
คีย์คือ Zote
และผูกค่าของ title
ตัวแปร
แนวคิดนี้ทำงานได้ดีเมื่อโครงสร้างข้อมูลซับซ้อน คุณสามารถกำหนดตัวแปรของคุณ และตรวจสอบค่าหรือประเภททั้งหมดในบรรทัดเดียว
นอกจากนี้ยังช่วยให้ภาษาที่พิมพ์แบบไดนามิกเช่น Elixir มีวิธีการโอเวอร์โหลด:
def process(%{"animal" => animal}) do IO.puts("The animal is: #{animal}") end def process(%{"plant" => plant}) do IO.puts("The plant is: #{plant}") end def process(%{"person" => person}) do IO.puts("The person is: #{person}") end
ขึ้นอยู่กับคีย์ของแฮชของอาร์กิวเมนต์ วิธีการต่างๆ จะถูกดำเนินการ
หวังว่านั่นจะแสดงให้คุณเห็นว่าการจับคู่รูปแบบมีประสิทธิภาพเพียงใด มีความพยายามมากมายในการจับคู่รูปแบบเข้ากับทับทิมด้วยอัญมณี เช่น noaidi, qo และ egison-ruby
Ruby 2.7 ยังมีการใช้งานของตัวเองซึ่งไม่ต่างจากอัญมณีเหล่านี้มากนัก และนี่คือวิธีการดำเนินการในปัจจุบัน
ไวยากรณ์การจับคู่รูปแบบทับทิม
การจับคู่รูปแบบใน Ruby ทำได้ผ่านคำสั่ง case
อย่างไรก็ตาม แทนที่จะใช้ normal when
ใช้คีย์เวิร์ด in
แทน unless
นี้ยังสนับสนุนการใช้คำสั่ง if
หรือ if:
case [variable or expression] in [pattern] ... in [pattern] if [expression] ... else ... end
คำสั่ง Case สามารถยอมรับตัวแปรหรือนิพจน์ได้ และจะจับคู่กับรูปแบบที่ให้ไว้ใน in clause สามารถระบุคำสั่ง if หรือ if ได้หลังรูปแบบ การตรวจสอบความเท่าเทียมกันที่นี่ยังใช้ ===
เช่นเดียวกับคำสั่งกรณีปกติ ซึ่งหมายความว่าคุณสามารถจับคู่ชุดย่อยและอินสแตนซ์ของคลาสได้ นี่คือตัวอย่างวิธีการใช้งานของคุณ:
อาร์เรย์ที่ตรงกัน
translation = ['th', 'เต้', 'ja', 'テイ'] case translation in ['th', orig_text, 'en', trans_text] puts "English translation: #{orig_text} => #{trans_text}" in ['th', orig_text, 'ja', trans_text] # this will get executed puts "Japanese translation: #{orig_text} => #{trans_text}" end
ในตัวอย่างข้างต้น การ translation
ตัวแปรจะจับคู่กับสองรูปแบบ:
['th', orig_text, 'en', trans_text]
และ ['th', orig_text, 'ja', trans_text]
ตรวจสอบว่าค่าในรูปแบบตรงกับค่าในตัวแปรการ translation
ในแต่ละดัชนีหรือไม่ หากค่าตรงกัน มันจะกำหนดค่าในตัวแปรการ translation
ให้กับตัวแปรในรูปแบบในแต่ละดัชนี
จับคู่แฮช
translation = {orig_lang: 'th', trans_lang: 'en', orig_txt: 'เต้', trans_txt: 'tae' } case translation in {orig_lang: 'th', trans_lang: 'en', orig_txt: orig_txt, trans_txt: trans_txt} puts "#{orig_txt} => #{trans_txt}" end
ในตัวอย่างข้างต้น ตัวแปรการ translation
ในขณะนี้คือแฮช มันถูกจับคู่กับแฮชอื่น in
ประโยค สิ่งที่เกิดขึ้นคือคำสั่ง case จะตรวจสอบว่าคีย์ทั้งหมดในรูปแบบตรงกับคีย์ในตัวแปรการ translation
หรือไม่ นอกจากนี้ยังตรวจสอบว่าค่าทั้งหมดสำหรับแต่ละคีย์ตรงกันหรือไม่ จากนั้นกำหนดค่าให้กับตัวแปรในแฮช
การจับคู่ชุดย่อย
การตรวจสอบคุณภาพที่ใช้ในการจับคู่รูปแบบเป็นไปตามตรรกะของ ===
หลายรูปแบบ
-
|
สามารถใช้เพื่อกำหนดรูปแบบหลายแบบสำหรับหนึ่งบล็อก
translation = ['th', 'เต้', 'ja', 'テイ'] case array in {orig_lang: 'th', trans_lang: 'ja', orig_txt: orig_txt, trans_txt: trans_txt} | ['th', orig_text, 'ja', trans_text] puts orig_text #=> เต้ puts trans_text #=> テイend
ในตัวอย่างข้างต้น ตัวแปรการ translation
จะจับคู่กับ {orig_lang: 'th', trans_lang: 'ja', orig_txt: orig_txt, trans_txt: trans_txt}
ทั้งแฮชและ ['th', orig_text, 'ja', trans_text]
อาร์เรย์

สิ่งนี้มีประโยชน์เมื่อคุณมีโครงสร้างข้อมูลประเภทต่างๆ เล็กน้อยซึ่งแสดงถึงสิ่งเดียวกัน และคุณต้องการให้โครงสร้างข้อมูลทั้งสองดำเนินการบล็อกโค้ดเดียวกัน
การกำหนดลูกศร
ในกรณีนี้ =>
สามารถใช้เพื่อกำหนดค่าที่ตรงกันให้กับตัวแปรได้
case ['I am a string', 10] in [Integer, Integer] => a # not reached in [String, Integer] => b puts b #=> ['I am a string', 10] end
สิ่งนี้มีประโยชน์เมื่อคุณต้องการตรวจสอบค่าภายในโครงสร้างข้อมูล แต่ยังผูกค่าเหล่านี้กับตัวแปรด้วย
ตัวดำเนินการพิน
ที่นี่ ตัวดำเนินการพินจะป้องกันไม่ให้ตัวแปรถูกกำหนดใหม่
case [1,2,2] in [a,a,a] puts a #=> 2 end
ในตัวอย่างข้างต้น ตัวแปร a ในรูปแบบจะจับคู่กับ 1, 2 และ 2 โดยจะถูกกำหนดให้เป็น 1 จากนั้นเป็น 2 จากนั้นจึงเปลี่ยนเป็น 2 นี่ไม่ใช่สถานการณ์ในอุดมคติ หากคุณต้องการตรวจสอบว่า ค่าในอาร์เรย์จะเหมือนกัน
case [1,2,2] in [a,^a,^a] # not reached in [a,b,^b] puts a #=> 1 puts b #=> 2 end
เมื่อใช้ตัวดำเนินการพิน ตัวดำเนินการพินจะประเมินตัวแปรแทนการกำหนดใหม่ ในตัวอย่างข้างต้น [1,2,2] ไม่ตรงกับ [a,^a,^a] เพราะในดัชนีแรก a ถูกกำหนดให้เป็น 1 ในอันดับที่สองและสาม a จะถูกประเมินเป็น 1 แต่เข้าคู่กัน 2
อย่างไรก็ตาม [a,b,^b] ตรงกับ [1,2,2] เนื่องจาก a ถูกกำหนดให้เป็น 1 ในดัชนีแรก, b ถูกกำหนดให้เป็น 2 ในดัชนีที่สอง จากนั้น ^b ซึ่งตอนนี้คือ 2 จะถูกจับคู่กับ 2 ในดัชนีที่สามจึงผ่าน
a = 1 case [2,2] in [^a,^a] #=> not reached in [b,^b] puts b #=> 2 end
ตัวแปรจากภายนอกคำสั่ง case ยังสามารถใช้ได้ดังที่แสดงในตัวอย่างด้านบน
ขีดล่าง ( _
) ตัวดำเนินการ
ขีดล่าง ( _
) ใช้เพื่อละเว้นค่า ลองดูในตัวอย่างสองสามตัวอย่าง:
case ['this will be ignored',2] in [_,a] puts a #=> 2 end
case ['a',2] in [_,a] => b puts a #=> 2 Puts b #=> ['a',2] end
ในสองตัวอย่างข้างต้น ค่าใดๆ ก็ตามที่ตรงกับ _
ผ่าน ในคำสั่งกรณีที่สอง =>
ตัวดำเนินการจะรวบรวมค่าที่ถูกละเว้นเช่นกัน
ใช้กรณีสำหรับการจับคู่รูปแบบใน Ruby
ลองนึกภาพว่าคุณมีข้อมูล JSON ต่อไปนี้:
{ nickName: 'Tae' realName: {firstName: 'Noppakun', lastName: 'Wongsrinoppakun'} username: 'tae8838' }
ในโครงการ Ruby ของคุณ คุณต้องการแยกวิเคราะห์ข้อมูลนี้และแสดงชื่อโดยมีเงื่อนไขดังต่อไปนี้:
- หากมีชื่อผู้ใช้อยู่ ให้ส่งคืนชื่อผู้ใช้
- หากมีชื่อเล่น ชื่อ และนามสกุล ให้ส่งคืนชื่อเล่น ชื่อ และนามสกุล
- หากไม่มีชื่อเล่น แต่มีชื่อและนามสกุลอยู่ ให้ส่งคืนชื่อและนามสกุล
- หากไม่มีเงื่อนไขใด ๆ ให้ส่งคืน "ผู้ใช้ใหม่"
นี่คือวิธีที่ฉันจะเขียนโปรแกรมนี้ใน Ruby ตอนนี้:
def display_name(name_hash) if name_hash[:username] name_hash[:username] elsif name_hash[:nickname] && name_hash[:realname] && name_hash[:realname][:first] && name_hash[:realname][:last] "#{name_hash[:nickname]} #{name_hash[:realname][:first]} #{name_hash[:realname][:last]}" elsif name_hash[:first] && name_hash[:last] "#{name_hash[:first]} #{name_hash[:last]}" else 'New User' end end
ตอนนี้เรามาดูกันว่าการจับคู่รูปแบบเป็นอย่างไร:
def display_name(name_hash) case name_hash in {username: username} username in {nickname: nickname, realname: {first: first, last: last}} "#{nickname} #{first} #{last}" in {first: first, last: last} "#{first} #{last}" else 'New User' end end
การตั้งค่าไวยากรณ์อาจเป็นเรื่องส่วนตัวเล็กน้อย แต่ฉันชอบเวอร์ชันที่ตรงกับรูปแบบมากกว่า เนื่องจากการจับคู่รูปแบบช่วยให้เราสามารถเขียนแฮชที่เราคาดหวัง แทนที่จะอธิบายและตรวจสอบค่าของแฮช สิ่งนี้ทำให้ง่ายต่อการเห็นภาพว่าข้อมูลใดบ้างที่คาดหวัง:
`{nickname: nickname, realname: {first: first, last: last}}`
แทน:
`name_hash[:nickname] && name_hash[:realname] && name_hash[:realname][:first] && name_hash[:realname][:last]`.
Deconstruct และ Deconstruct_keys
มีการแนะนำวิธีการพิเศษใหม่สองวิธีใน Ruby 2.7: deconstruct
และ deconstruct_keys
เมื่ออินสแตนซ์ของคลาสถูกจับคู่กับอาร์เรย์หรือแฮช จะมีการเรียก deconstruct
หรือ deconstruct_keys
ตามลำดับ
ผลลัพธ์จากวิธีการเหล่านี้จะถูกนำมาใช้เพื่อจับคู่กับรูปแบบ นี่คือตัวอย่าง:
class Coordinate attr_accessor :x, :y def initialize(x, y) @x = x @y = y end def deconstruct [@x, @y] end def deconstruct_key {x: @x, y: @y} end end
รหัสกำหนดคลาสที่เรียกว่า Coordinate
มี x และ y เป็นคุณลักษณะ นอกจากนี้ยังมีวิธีการ deconstruct
และ deconstruct_keys
ที่กำหนดไว้
c = Coordinates.new(32,50) case c in [a,b] pa #=> 32 pb #=> 50 end
ที่นี่ อินสแตนซ์ของ Coordinate
กำลังถูกกำหนดและรูปแบบที่ตรงกับอาร์เรย์ สิ่งที่เกิดขึ้นที่นี่คือการเรียก Coordinate#deconstruct
และผลลัพธ์จะถูกใช้เพื่อจับคู่กับอาร์เรย์ [a,b]
ที่กำหนดไว้ในรูปแบบ
case c in {x:, y:} px #=> 32 py #=> 50 end
ในตัวอย่างนี้ อินสแตนซ์เดียวกันของ Coordinate
กำลังจับคู่รูปแบบกับแฮช ในกรณีนี้ ผลลัพธ์ของ Coordinate#deconstruct_keys
จะถูกใช้เพื่อจับคู่กับแฮช {x: x, y: y}
ที่กำหนดไว้ในรูปแบบ
คุณสมบัติการทดลองที่น่าตื่นเต้น
เมื่อมีประสบการณ์การจับคู่รูปแบบใน Elixir เป็นครั้งแรก ฉันคิดว่าคุณลักษณะนี้อาจรวมถึงการโอเวอร์โหลดเมธอดและการใช้งานกับไวยากรณ์ที่ต้องการเพียงบรรทัดเดียว อย่างไรก็ตาม Ruby ไม่ใช่ภาษาที่สร้างขึ้นโดยคำนึงถึงการจับคู่รูปแบบ ดังนั้นจึงเข้าใจได้
การใช้คำสั่ง case น่าจะเป็นวิธีการที่ใช้ง่ายและไม่ส่งผลต่อโค้ดที่มีอยู่ (นอกเหนือจากวิธี deconstruct
และ deconstruct_keys
) การใช้คำสั่ง case จริง ๆ แล้วคล้ายกับการใช้การจับคู่รูปแบบของ Scala
โดยส่วนตัวแล้ว ฉันคิดว่าการจับคู่รูปแบบเป็นคุณสมบัติใหม่ที่น่าตื่นเต้นสำหรับนักพัฒนา Ruby มีศักยภาพในการทำให้โค้ดสะอาดขึ้นมาก และทำให้ Ruby รู้สึกทันสมัยและน่าตื่นเต้นขึ้นเล็กน้อย ฉันชอบที่จะดูว่าผู้คนใช้สิ่งนี้อย่างไรและคุณลักษณะนี้จะพัฒนาไปอย่างไรในอนาคต