อาร์เรย์ของความเป็นไปได้: คู่มือการจับคู่รูปแบบทับทิม

เผยแพร่แล้ว: 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 ของคุณ คุณต้องการแยกวิเคราะห์ข้อมูลนี้และแสดงชื่อโดยมีเงื่อนไขดังต่อไปนี้:

  1. หากมีชื่อผู้ใช้อยู่ ให้ส่งคืนชื่อผู้ใช้
  2. หากมีชื่อเล่น ชื่อ และนามสกุล ให้ส่งคืนชื่อเล่น ชื่อ และนามสกุล
  3. หากไม่มีชื่อเล่น แต่มีชื่อและนามสกุลอยู่ ให้ส่งคืนชื่อและนามสกุล
  4. หากไม่มีเงื่อนไขใด ๆ ให้ส่งคืน "ผู้ใช้ใหม่"

นี่คือวิธีที่ฉันจะเขียนโปรแกรมนี้ใน 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 รู้สึกทันสมัยและน่าตื่นเต้นขึ้นเล็กน้อย ฉันชอบที่จะดูว่าผู้คนใช้สิ่งนี้อย่างไรและคุณลักษณะนี้จะพัฒนาไปอย่างไรในอนาคต