Sắp xếp mảng trong Ruby rất dễ dàng!
Bạn không cần viết bất kỳ thuật toán phức tạp nào để đạt được kết quả bạn muốn.
Cần gì, thì?
Hiểu các phương thức sắp xếp tích hợp sẵn của Ruby.
Những phương thức này:
- sort
- sort_by
- sort!
Các phương thức này hoạt động như thế nào và tại sao chúng khác nhau?
Đó là những gì bạn sẽ khám phá trong bài viết này.
Bạn sẽ học các cách khác nhau để sắp xếp mảng, bắt đầu với phương thức sort, sau đó xem sort_by cho sắp xếp nâng cao (theo nhiều giá trị) và hơn nữa.
Chúng ta bắt đầu nào!
Học sử dụng Phương thức Sort & Sort! trong Ruby
Hình thức sắp xếp cơ bản nhất được cung cấp bởi phương thức sort của Ruby, được định nghĩa bởi module Enumerable.
Hãy xem ví dụ:
numbers = [5,3,2,1] numbers.sort # [1,2,3,5]
Lưu ý rằng sort sẽ trả về một mảng mới với kết quả.
Một mảng các phần tử đã sắp xếp!
Cũng có thể sắp xếp "ngay tại chỗ" (in-place) bằng phương thức sort!.
Điều này có nghĩa là mảng gốc sẽ thay đổi thay vì tạo mảng mới, điều này có thể tốt cho hiệu suất.
Sắp xếp Tùy chỉnh với sort_by
Với phương thức sort_by, bạn có thể thực hiện sắp xếp nâng cao và thú vị hơn.
Bạn sẽ có thể:
- Sắp xếp theo độ dài chuỗi
- Sắp xếp theo nội dung chuỗi
- Sắp xếp theo số chẵn hoặc lẻ
Bạn có thể làm điều này với phương thức sort_by và một khối Ruby.
Ví dụ:
strings = %w(foo test blog a) strings.sort_by(&:length) # ["a", "foo", "test", "blog"]
Cái này hoạt động như thế nào?
Vâng, phương thức sort_by mong đợi một giá trị số, đó là lý do length hoạt động.
Nếu bạn hiểu điều này, bạn có thể sử dụng phương thức này để làm những điều thú vị, như sắp xếp các từ bắt đầu bằng chữ cái in hoa và để mọi thứ khác nguyên vẹn.
Như thế này:
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"
Cũng có thể thực hiện sắp xếp tùy chỉnh bằng phương thức sort thông thường với một khối.
Đây là ví dụ:
strings = %w(foo test blog a) strings.sort { |a,b| a.length <=> b.length } # ["a", "foo", "test", "blog"]
Lưu ý: Ký hiệu
<=>này được gọi là "toán tử phi thuyền" và là một phương thức bạn có thể triển khai trong lớp của mình. Nó nên trả về 1 (lớn hơn), 0 (bằng) hoặc -1 (nhỏ hơn).
Nói chung, tôi thích phương thức sort_by vì ý định rõ ràng hơn, dễ đọc hơn và cũng nhanh hơn một chút.
Sắp xếp theo Thứ tự Ngược
Sắp xếp ngược thì sao?
Bạn có thể sử dụng phương thức reverse sau khi sắp xếp, hoặc bạn có thể dùng một khối và đặt dấu trừ trước thứ bạn đang sắp xếp.
Cho tôi ví dụ:
strings = %w(foo test blog a) strings.sort_by { |str| -str.length } # ["blog", "test", "foo", "a"]
Không hoa mỹ, nhưng nó hoạt động! 🙂
Sắp xếp Alpha-số
Giả sử bạn muốn sắp xếp theo số một danh sách chuỗi chứa số.
Như thế này:
music = %w(21.mp3 10.mp3 5.mp3 40.mp3)
Mặc định, bạn sẽ không nhận được danh sách được sắp xếp như bạn muốn.
Ví dụ:
music.sort # ["10.mp3", "21.mp3", "40.mp3", "5.mp3"]
Nhưng bạn có thể khắc phục điều này bằng sort_by:
music.sort_by { |s| s.scan(/\d+/).first.to_i } # ["5.mp3", "10.mp3", "21.mp3", "40.mp3"]
Tôi dùng biểu thức chính quy (\d+) để khớp số, sau đó lấy số đầu tiên (first) và chuyển đổi thành đối tượng integer (to_i).
Cách Sắp xếp Hash trong Ruby
Bạn không bị giới hạn chỉ sắp xếp mảng, bạn cũng có thể sắp xếp hash.
Ví dụ:
hash = {coconut: 200, orange: 50, bacon: 100} hash.sort_by(&:last) # [[:orange, 50], [:bacon, 100], [:coconut, 200]]
Điều này sẽ sắp xếp theo giá trị, nhưng hãy chú ý điều thú vị ở đây, thứ bạn nhận được không phải là hash.
Bạn nhận được một mảng đa chiều khi sắp xếp hash.
Để chuyển đổi ngược lại thành hash, bạn có thể dùng phương thức Array#to_h.
Sắp xếp theo Nhiều Giá trị
Bạn có thể muốn sắp xếp thứ gì đó theo nhiều thuộc tính, nghĩa là bạn sắp xếp trước theo ngày (ví dụ), nhưng vì bạn có nhiều thứ cùng ngày nên có sự hòa.
Để phá vỡ sự hòa, bạn có thể dùng thuộc tính thứ hai.
Ví dụ:
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] }
Chìa khóa ở đây là mảng bên trong khối sort_by.
Nơi bạn đặt thuộc tính sắp xếp chính làm phần tử đầu tiên của mảng (event.date) và sau đó thuộc tính phá hòa thứ hai (event.name).
Triển khai QuickSort
Chỉ cho vui, hãy triển khai phương thức sắp xếp của riêng chúng ta. Nó sẽ chậm hơn phương thức sắp xếp tích hợp sẵn, nhưng vẫn là bài tập thú vị nếu bạn thích khoa học máy tính.
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]
Ý tưởng của quick sort là chọn một số ngẫu nhiên sau đó chia danh sách chúng ta đang sắp xếp thành hai nhóm.
Một nhóm là các số nhỏ hơn số được chọn và nhóm kia là các số lớn hơn số được chọn.
Sau đó chúng ta chỉ lặp lại thao tác này cho đến khi danh sách được sắp xếp.
Benchmark
Hãy xem các phương thức sắp xếp này so sánh như thế nào về hiệu suất.
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
Như bạn thấy, phương thức sort thông thường nhanh hơn nhiều so với sort_by, nhưng nó không linh hoạt bằng trừ khi bạn dùng khối.
Video
Tóm tắt
Bạn đã học cách dùng phương thức sort và sort_by để sắp xếp mảng và hash theo nhiều cách. Bạn cũng đã học về sự khác biệt hiệu suất và cách triển khai thuật toán quicksort.
Đây là những gì bạn nên nhớ:
- Bạn có thể dùng phương thức
sorttrên mảng, hash, hoặc đối tượng Enumerable khác và bạn sẽ nhận được hành vi sắp xếp mặc định (sắp xếp dựa trên toán tử<=>) - Bạn có thể dùng
sortvới khối, và hai đối số khối, để định nghĩa cách một đối tượng khác biệt so với đối tượng khác (khối nên trả về 1, 0, hoặc -1) - Bạn có thể dùng
sort_byvới khối, và một đối số, để định nghĩa một thuộc tính cho mỗi đối tượng sẽ được dùng làm cơ sở sắp xếp (độ dài mảng, thuộc tính đối tượng, chỉ mục, v.v.). Khối nên trả về giá trị integer xác định vị trí của đối tượng trong mảng đã sắp xếp.
Đừng quên chia sẻ bài viết này để nhiều người hơn có thể học được. 🙂