การจัดเรียงอาร์เรย์ใน Ruby ทำได้ง่ายมาก!
คุณ ไม่ต้องเขียนอัลกอริทึมซับซ้อน เพื่อให้ได้ผลลัพธ์ที่ต้องการ
ต้องการอะไรบ้าง?
เข้าใจเมธอดจัดเรียง built-in ของ Ruby
คือเมธอดเหล่านี้:
- sort
- sort_by
- sort!
เมธอดเหล่านี้ทำงานอย่างไร และต่างกันยังไง?
นั่นคือสิ่งที่คุณจะค้นพบในบทความนี้
คุณจะเรียนรู้วิธีจัดเรียงอาร์เรย์ต่างๆ เริ่มจาก sort แล้วค่อยไปดู sort_by สำหรับการจัดเรียงขั้นสูง (หลายค่า) และอื่นๆ
มาเริ่มกัน!
เรียนรู้การใช้เมธอด Sort และ Sort! ใน Ruby
รูปแบบพื้นฐานสุดของการจัดเรียงมาจากเมธอด sort ของ Ruby ซึ่งถูกกำหนดโดยโมดูล Enumerable
มาดูตัวอย่าง:
numbers = [5,3,2,1] numbers.sort # [1,2,3,5]
สังเกตว่า sort จะคืนอาร์เรย์ใหม่พร้อมผลลัพธ์
อาร์เรย์ของอิลิเมนต์ที่จัดเรียงแล้ว!
ยังสามารถจัดเรียง "in-place" ได้ด้วยเมธอด sort!
หมายความว่า อาร์เรย์เดิมจะ เปลี่ยนแปลง แทนที่จะสร้างใหม่ ซึ่งอาจดีต่อประสิทธิภาพ
การจัดเรียงแบบกำหนดเองด้วย sort_by
ด้วยเมธอด sort_by คุณทำการจัดเรียงขั้นสูงและน่าสนใจได้
คุณจะสามารถ:
- จัดเรียงตามความยาวสตริง
- จัดเรียงตามเนื้อหาสตริง
- จัดเรียงตามว่าตัวเลขเป็นคู่หรือคี่
ทำได้ด้วย sort_by และบล็อก Ruby
เช่น:
strings = %w(foo test blog a) strings.sort_by(&:length) # ["a", "foo", "test", "blog"]
มันทำงานยังไง?
เมธอด sort_by คาดหวังค่าตัวเลข นั่นคือเหตุผลที่ length ใช้ได้
ถ้าคุณเข้าใจนี้ คุณสามารถใช้เมธอดนี้ทำอย่างน่าสนใจได้ เช่น จัดเรียงคำที่ขึ้นต้นด้วยตัวใหญ่ และทิ้งส่วนที่เหลือไว้ที่เดิม
แบบนี้:
def sort_by_capital_word(text) text .split .sort_by { |w| w[0].match?(/[A-Z]/) ? 0 : 1 } .join(" ") end sort_by_capital_word("calendar Cat tap Lamp") # "Cat Lamp calendar tap" ยังสามารถจัดเรียงแบบกำหนดเองด้วย sort ธรรมดาและบล็อกได้
ตัวอย่าง:
strings = %w(foo test blog a) strings.sort { |a,b| a.length <=> b.length } # ["a", "foo", "test", "blog"] หมายเหตุ: สัญลักษณ์
<=>เรียกว่า "spaceship operator" เป็นเมธอดที่คุณสามารถนำไปใช้ในคลาสของตัวเองได้ ควรคืนค่า 1 (มากกว่า), 0 (เท่ากัน) หรือ -1 (น้อยกว่า)
โดยทั่วไป ผมชอบ sort_by มากกว่า เพราะเจตนาชัดเจนกว่า อ่านง่ายกว่า และเร็วกว่าเล็กน้อย
จัดเรียงย้อนกลับ (Reverse Order)
การจัดเรียงย้อนกลับทำยังไง?
คุณอาจใช้ reverse หลังจัดเรียง หรือใช้บล็อกและวางเครื่องหมายลบหน้าสิ่งที่จัดเรียง
ตัวอย่าง:
strings = %w(foo test blog a) strings.sort_by { |str| -str.length } # ["blog", "test", "foo", "a"] ไม่หรูหรา แต่ใช้ได้! 🙂
การจัดเรียงอัลฟา न्यूमอริก (Alphanumeric Sorting)
สมมติอยากจัดเรียงตัวเลขในลิสต์สตริงที่มีตัวเลข
แบบนี้:
music = %w(21.mp3 10.mp3 5.mp3 40.mp3)
ค่าเริ่มต้นจะไม่ได้ลิสต์ที่จัดเรียงเหมือนที่อยากได้
ตัวอย่าง:
music.sort # ["10.mp3", "21.mp3", "40.mp3", "5.mp3"]
แต่แก้ได้ด้วย sort_by:
music.sort_by { |s| s.scan(/\d+/).first.to_i } # ["5.mp3", "10.mp3", "21.mp3", "40.mp3"] ผมใช้ regular expression (\d+) จับตัวเลข แล้วเอาตัวแรก (first) และแปลงเป็น integer (to_i)
วิธีจัดเรียง Hash ใน Ruby
คุณไม่จำกัดแค่อาร์เรย์ สามารถจัดเรียง hash ได้
ตัวอย่าง:
hash = {coconut: 200, orange: 50, bacon: 100} hash.sort_by(&:last) # [[:orange, 50], [:bacon, 100], [:coconut, 200]] นี่จัดเรียงตามค่า แต่สังเกตสิ่งน่าสนใจ: สิ่งที่ได้กลับมา ไม่ใช่ hash
คุณได้อาร์เรย์หลายมิติ (multi-dimensional array) ตอนจัดเรียง hash
เพื่อแปลงกลับเป็น hash ใช้เมธอด Array#to_h
การจัดเรียงหลายค่า (Sorting By Multiple Values)
คุณอาจอยากจัดเรียงหลายแอตทริบิวต์ คือจัดเรียงตามวันที่ก่อน (เช่น) แต่มีหลายอย่างที่วันที่เดียวกันจึงมีการเสมอ (tie)
เพื่อแยก tie ใช้แอตทริบิวต์รอง
ตัวอย่าง:
Event = Struct.new(:name, :date) events = [] events << Event.new("book sale", Time.now) events << Event.new("course sale", Time.now) events << Event.new("new subscriber", Time.now) events << Event.new("course sale", Time.now + 1.day) events.sort_by { |event| [event.date, event.name] } กุญแจสำคัญคืออาร์เรย์ภายในบล็อก sort_by
คุณตั้งแอตทริบิวต์หลักเป็นอิลิเมนต์แรก (event.date) และแอตทริบิวต์รองสำหรับแยก tie (event.name)
การนำ QuickSort ไปใช้งาน
เพื่อความสนุก ลองนำ sorting method ของตัวเองมาใช้งาน นี่จะช้ากว่า built-in methods แต่เป็นแบบฝึกหัดน่าสนใจถ้าชอบ computer science
def quick_sort(list) return [] if list.empty? groups = list.group_by { |n| n <=> list.first } less_than = groups[-1] || [] first = groups[0] || [] greater_than = groups[1] || [] quick_sort(less_than) + first + quick_sort(greater_than) end p quick_sort [3, 7, 2, 1, 8, 12] # [1, 2, 3, 7, 8, 12] แนวคิด quick sort คือ เลือกตัวเลขสุ่ม แล้วแบ่งลิสต์ที่จัดเรียงเป็น 2 กลุ่ม
กลุ่มหนึ่งคือตัวเลขน้อยกว่าตัวที่เลือก อีกกลุ่มคือตัวเลขมากกว่า
แล้วทำซ้ำจนกว่าลิสต์จะเรียงแล้ว
Benchmarks
มาเปรียบเทียบเมธอดจัดเรียงทั้งหมดด้านประสิทธิภาพ
Ruby 2.4.0:
sort!: 1405.8 i/s sort: 1377.6 i/s - same-ish: difference falls within error sort_by reverse: 196.6 i/s - 7.15x slower sort_by: 183.7 i/s - 7.65x slower sort_by minus: 172.3 i/s - 8.16x slower sort with block: 164.1 i/s - 8.57x slower
เห็นได้ว่า sort ธรรมดาเร็วกว่า sort_by มาก แต่ไม่ยืดหยุ่นเท่าไรนัก เว้นแต่ใช้บล็อก
วิดีโอ
สรุป
คุณเรียนรู้การใช้ sort และ sort_by จัดเรียงอาร์เรย์และแฮชแบบต่างๆ รู้ความต่างด้านประสิทธิภาพ และนำ quicksort ไปใช้งาน
สิ่งที่ควรจำ:
- ใช้
sortบนอาร์เรย์ แฮช หรือ Enumerable อื่น ได้พฤติกรรมเริ่มต้น (จัดเรียงตามออปเปอเรเตอร์<=>) - ใช้
sortกับบล็อก และอาร์กิวเมนต์ 2 ตัว กำหนดว่าออบเจกต์หนึ่งต่างจากอีกอันอย่างไร (บล็อกควรคืน 1, 0, หรือ -1) - ใช้
sort_byกับบล็อก และอาร์กิวเมนต์ 1 ตัว กำหนดแอตทริบิวต์หนึ่งต่อออบเจกต์หนึ่ง เป็นพื้นฐานการจัดเรียง (ความยาวอาร์เรย์, แอตทริบิวต์ออบเจกต์, index, ฯลฯ) บล็อกควรคืนค่า integer กำหนดตำแหน่งในอาร์เรย์ที่จัดเรียงแล้ว
อย่าลืม แชร์โพสต์นี้ ให้คนอื่นเรียนรู้ได้ 🙂