Menu

Sắp xếp slice Golang: slices.Sort, SortFunc và nhiều trường

Sắp xếp slice trong Go với slices.Sort và slices.SortFunc, sắp xếp struct theo một hoặc nhiều trường bằng cmp.Compare, giữ thứ tự các phần tử bằng nhau với sắp xếp ổn định, và đọc hiểu code sort.Slice cũ.

Trang này có trình soạn thảo chạy trực tiếp. Sửa, chạy và xem đầu ra ngay lập tức.

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.Sliceslices.SortFunc
Hàm nhậnchỉ số i, jphần tử a, b
Trả vềbool (i có nhỏ hơn j không)int (âm, 0, dương)
An toàn kiểunhận any, dùng reflectiongeneric, kiểm tra lúc biên dịch
Tốc độchậm hơnnhanh 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 Sort trả về slice đã sắp xếp. Nó sắp xếp tại chỗ và không trả về gì. Dùng slices.Sorted(slices.Values(s)) nếu muốn một slice mới đã sắp xếp, hoặc slices.Clone trước.
  • Cho rằng các phần tử bằng nhau giữ nguyên thứ tự. Chỉ các biến thể Stable mới bảo đảm điều đó.
  • So sánh bằng phép trừ. a - b bị tràn số. Hãy dùng cmp.Compare.
  • Sắp xếp số thực có NaN. cmp.Compare xế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ánh a < b tự 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.

Hình minh họa các ngôn ngữ lập trình của Coddy

Học lập trình cùng Coddy

Bắt đầu