Sắp xếp các kiểu có sẵn
slices.Sort sắp xếp mọi slice có kiểu phần tử có thứ tự: số nguyên, số thực và chuỗi. Hàm sắp xếp tại chỗ và không trả về gì.
Đầu ra:
[3 7 19 42 88]
[Bob Carl alice lisa]
true
2 true
Chuỗi được sắp xếp theo byte, nên mọi chữ ASCII viết hoa đều đứng trước mọi chữ viết thường. Với tên người, đó hiếm khi là điều người dùng mong đợi. Phần tiếp theo sẽ sửa điều này.
Package slices có từ Go 1.21. Hàm Sort của nó là pattern-defeating quicksort: O(n log n), tại chỗ, và không ổn định.
Thứ tự tùy chỉnh với SortFunc
slices.SortFunc nhận một hàm so sánh func(a, b T) int. Trả về số âm khi a nên đứng trước, số dương khi b nên đứng trước, và 0 khi hai phần tử bằng nhau. Hàm hỗ trợ cmp.Compare trả về đúng như vậy cho các kiểu có thứ tự.
strings.ToLower bên trong phép so sánh cấp phát một chuỗi mới mỗi khi đầu vào có chữ hoa, và phép so sánh chạy khoảng n log n lần. Với slice lớn, hãy tính khóa chữ thường một lần trước. Với tên trong các ngôn ngữ khác (dấu, quy tắc theo vùng), hãy dùng golang.org/x/text/collate, nằm ngoài thư viện chuẩn.
Hàm so sánh phải nhất quán: nếu nó nói a đứng trước b, thì nó phải nói b đứng sau a. Một hàm phá vỡ điều này (ví dụ trả về -1 mỗi khi hai giá trị khác nhau) sẽ cho ra slice sắp xếp sai mà không báo lỗi. Trừ hai số nguyên (return a - b) trông gọn nhưng bị tràn số với giá trị lớn; hãy dùng cmp.Compare.
Sắp xếp struct
Hàm so sánh nhận các phần tử, nên sắp xếp struct theo một trường cũng chỉ là lời gọi y như vậy:
Ở đây các nhân viên có lương bằng nhau có thể ra theo thứ tự bất kỳ, vì SortFunc không ổn định. Nếu cần một thứ tự chắc chắn, hãy phân định bằng thêm trường hoặc dùng sắp xếp ổn định.
Sắp xếp theo nhiều trường
So sánh trường quan trọng nhất trước và chỉ chuyển sang trường tiếp theo khi hòa. cmp.Or (Go 1.22) trả về đối số khác 0 đầu tiên, nên mọi thứ gói gọn trong một dòng:
Đầu ra:
eng 150 Cy
eng 120 Ana
eng 120 Eve
sales 90 Bob
sales 90 Dee
Mọi phép so sánh đều được tính kể cả khi phép đầu tiên đã quyết định, vì chúng chỉ là đối số thông thường. Với so sánh trường thì tốn rất ít. Khi một tiêu chí phụ tốn kém, hãy tự viết chuỗi if c := ...; c != 0 { return c }.
Sắp xếp ổn định
Sắp xếp ổn định giữ các phần tử bằng nhau theo thứ tự ban đầu. Điều này quan trọng khi đầu vào vốn đã có thứ tự ý nghĩa, ví dụ các bản ghi đã sắp theo thời gian mà giờ bạn muốn gom theo người dùng.
Chương trình in ra [{ana 2} {ana 4} {bob 1} {bob 3} {bob 5}]: trong mỗi người dùng, thứ tự ban đầu được giữ nguyên. Sắp xếp ổn định tốn nhiều công hơn, nên chỉ dùng khi thứ tự của các phần tử bằng nhau có ý nghĩa.
Sắp xếp một map
Map không có thứ tự. Để hiển thị map theo thứ tự khóa, hãy sắp xếp các khóa: slices.Sorted(maps.Keys(m)) (Go 1.23). Để sắp xếp theo giá trị, hãy sắp xếp các khóa bằng một phép so sánh tra cứu giá trị:
Tiêu chí phụ theo từ là cần thiết: thiếu nó, chan và slice (cùng là 7) sẽ in ra theo thứ tự khác nhau giữa các lần chạy, vì khóa lấy ra từ map theo thứ tự ngẫu nhiên. Xem thêm ở trang map.
Package sort: sort.Slice và các hàm liên quan
Trước Go 1.21, sắp xếp đi qua package sort. Bạn sẽ gặp nó trong rất nhiều code hiện có:
sort.Ints(nums)
sort.Strings(names)
sort.Slice(staff, func(i, j int) bool {
return staff[i].Salary < staff[j].Salary
})
sort.SliceStable(staff, func(i, j int) bool { ... })
Những khác biệt nên biết:
sort.Slice | slices.SortFunc | |
|---|---|---|
| Hàm nhận | chỉ số i, j | phần tử a, b |
| Trả về | bool (i có nhỏ hơn j không) | int (âm, 0, dương) |
| An toàn kiểu | nhận any, dùng reflection | generic, kiểm tra lúc biên dịch |
| Tốc độ | chậm hơn | nhanh hơn |
Một lỗi hay gặp với sort.Slice là closure tham chiếu tới một slice khác với slice đang được sắp xếp, vì hàm less truy cập theo vị trí. SortFunc không thể mắc lỗi này vì nó đưa thẳng phần tử cho bạn.
Kiểu sort.Interface (Len, Less, Swap) là dạng cũ nhất. Nó vẫn là cách để sắp xếp dữ liệu không nằm trong một slice duy nhất, như hai slice song song phải di chuyển cùng nhau. Từ Go 1.22, sort.Ints, sort.Strings và sort.Float64s chỉ đơn giản gọi slices.Sort.
Lỗi thường gặp
- Tưởng
Sorttrả về slice đã sắp xếp. Nó sắp xếp tại chỗ và không trả về gì. Dùngslices.Sorted(slices.Values(s))nếu muốn một slice mới đã sắp xếp, hoặcslices.Clonetrước. - Cho rằng các phần tử bằng nhau giữ nguyên thứ tự. Chỉ các biến thể
Stablemới bảo đảm điều đó. - So sánh bằng phép trừ.
a - bbị tràn số. Hãy dùngcmp.Compare. - Sắp xếp số thực có NaN.
cmp.Comparexếp NaN trước mọi giá trị khác, giúp việc sắp xếp nhất quán. Phép so sánha < btự viết thì không.
Câu hỏi thường gặp
Làm sao sắp xếp một slice trong Go?
Với số và chuỗi, gọi slices.Sort(s) (Go 1.21). Hàm này sắp xếp tại chỗ theo thứ tự tăng dần. Với kiểu khác, hoặc thứ tự khác, hãy dùng slices.SortFunc(s, func(a, b T) int { ... }), trong đó hàm trả về số âm nếu a đứng trước, số dương nếu b đứng trước, và 0 nếu hai phần tử bằng nhau.
Làm sao sắp xếp slice theo thứ tự giảm dần trong Go?
Đổi chỗ hai đối số trong phép so sánh: slices.SortFunc(s, func(a, b int) int { return cmp.Compare(b, a) }). Hoặc sắp xếp tăng dần rồi gọi slices.Reverse(s).
Làm sao sắp xếp slice struct theo nhiều trường trong Go?
So sánh trường đầu tiên, và chỉ chuyển sang trường tiếp theo khi trường đó bằng nhau. cmp.Or (Go 1.22) làm đúng việc này: return cmp.Or(cmp.Compare(a.Dept, b.Dept), cmp.Compare(b.Salary, a.Salary), strings.Compare(a.Name, b.Name)) trả về kết quả khác 0 đầu tiên.
sort.Slice và slices.SortFunc khác nhau thế nào?
sort.Slice(s, func(i, j int) bool) là API cũ: nó nhận một hàm less trên các chỉ số và dùng reflection để đổi chỗ phần tử. slices.SortFunc(s, func(a, b T) int) là hàm generic, được kiểm tra kiểu, nhận trực tiếp các phần tử và chạy nhanh hơn. Code mới nên dùng package slices; sort.Slice vẫn phổ biến trong code viết trước Go 1.21.