Skip to content

Commit 0668c35

Browse files
authored
Merge pull request #67 from the-origin/master
add sort for []interface
2 parents d8339c2 + 4962c08 commit 0668c35

File tree

1 file changed

+188
-14
lines changed

1 file changed

+188
-14
lines changed

chapter03/03.1.md

+188-14
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ Sort() 方法惟一的参数就是待排序的数据集合。
4242

4343
下面是一个使用 sort 包对学生成绩排序的示例:
4444

45-
```golang
45+
```go
4646
package main
4747

4848
import (
@@ -101,7 +101,7 @@ func main() {
101101
[{leao 90} {hikerell 91} {alan 95} {acmfly 96}]
102102

103103
该示例实现的是升序排序,如果要得到降序排序结果,其实只要修改 Less() 函数:
104-
```golang
104+
```go
105105
//Less(): 成绩降序排序 , 只将小于号修改为大于号
106106
func (s StuScores) Less(i, j int) bool {
107107
return s[i].score > s[j].score
@@ -112,7 +112,7 @@ func (s StuScores) Less(i, j int) bool {
112112
func Reverse(data Interface) Interface
113113

114114
我们可以看到 Reverse() 返回的一个 sort.Interface 接口类型,整个 Reverse() 的内部实现比较有趣:
115-
```golang
115+
```go
116116
// 定义了一个 reverse 结构类型,嵌入 Interface 接口
117117
type reverse struct {
118118
Interface
@@ -130,7 +130,7 @@ func (s StuScores) Less(i, j int) bool {
130130
}
131131
```
132132
了解内部原理后,可以在学生成绩排序示例中使用 Reverse() 来实现成绩升序排序:
133-
```golang
133+
```go
134134
sort.Sort(sort.Reverse(stus))
135135
fmt.Println(stus)
136136
```
@@ -145,7 +145,7 @@ func (s StuScores) Less(i, j int) bool {
145145
146146
Search() 函数一个常用的使用方式是搜索元素 x 是否在已经升序排好的切片 s 中:
147147

148-
```golang
148+
```go
149149
x := 11
150150
s := []int{3, 6, 8, 11, 45} // 注意已经升序排序
151151
pos := sort.Search(len(s), func(i int) bool { return s[i] >= x })
@@ -158,7 +158,7 @@ Search() 函数一个常用的使用方式是搜索元素 x 是否在已经升
158158

159159
官方文档还给出了一个猜数字的小程序:
160160

161-
```golang
161+
```go
162162
func GuessingGame() {
163163
var s string
164164
fmt.Printf("Pick an integer from 0 to 100.\n")
@@ -181,7 +181,7 @@ func GuessingGame() {
181181

182182
*sort*包定义了一个 IntSlice 类型,并且实现了 sort.Interface 接口:
183183

184-
```golang
184+
```go
185185
type IntSlice []int
186186
func (p IntSlice) Len() int { return len(p) }
187187
func (p IntSlice) Less(i, j int) bool { return p[i] < p[j] }
@@ -192,34 +192,34 @@ func GuessingGame() {
192192
func (p IntSlice) Search(x int) int { return SearchInts(p, x) }
193193
```
194194
并且提供的 sort.Ints() 方法使用了该 IntSlice 类型:
195-
```goalng
195+
```go
196196
func Ints(a []int) { Sort(IntSlice(a)) }
197197
```
198198

199199
所以,对[]int 切片排序更常使用 sort.Ints(),而不是直接使用 IntSlice 类型:
200200

201-
```golang
201+
```go
202202
s := []int{5, 2, 6, 3, 1, 4} // 未排序的切片数据
203203
sort.Ints(s)
204204
fmt.Println(s) // 将会输出[1 2 3 4 5 6]
205205
```
206206
如果要使用降序排序,显然要用前面提到的 Reverse() 方法:
207207

208-
```golang
208+
```go
209209
s := []int{5, 2, 6, 3, 1, 4} // 未排序的切片数据
210210
sort.Sort(sort.Reverse(sort.IntSlice(s)))
211211
fmt.Println(s) // 将会输出[6 5 4 3 2 1]
212212
```
213213

214214
如果要查找整数 x 在切片 a 中的位置,相对于前面提到的 Search() 方法,*sort*包提供了 SearchInts():
215215

216-
```golang
216+
```go
217217
func SearchInts(a []int, x int) int
218218
```
219219
注意,SearchInts() 的使用条件为:**切片 a 已经升序排序**
220220
以下是一个错误使用的例子:
221221

222-
```golang
222+
```go
223223
s := []int{5, 2, 6, 3, 1, 4} // 未排序的切片数据
224224
fmt.Println(sort.SearchInts(s, 2)) // 将会输出 0 而不是 1
225225
```
@@ -228,7 +228,7 @@ func GuessingGame() {
228228

229229
实现与 Ints 类似,只看一下其内部实现:
230230

231-
```golang
231+
```go
232232
type Float64Slice []float64
233233

234234
func (p Float64Slice) Len() int { return len(p) }
@@ -239,9 +239,11 @@ func GuessingGame() {
239239
```
240240
与 Sort()、IsSorted()、Search() 相对应的三个方法:
241241

242+
```go
242243
func Float64s(a []float64)
243244
func Float64sAreSorted(a []float64) bool
244245
func SearchFloat64s(a []float64, x float64) int
246+
```
245247

246248
要说明一下的是,在上面 Float64Slice 类型定义的 Less 方法中,有一个内部函数 isNaN()。
247249
isNaN() 与*math*包中 IsNaN() 实现完全相同,*sort*包之所以不使用 math.IsNaN(),完全是基于包依赖性的考虑,应当看到,*sort*包的实现不依赖与其他任何包。
@@ -252,7 +254,7 @@ isNaN() 与*math*包中 IsNaN() 实现完全相同,*sort*包之所以不使用
252254

253255
实现与 Ints 类似,只看一下其内部实现:
254256

255-
```golang
257+
```go
256258
type StringSlice []string
257259

258260
func (p StringSlice) Len() int { return len(p) }
@@ -268,6 +270,178 @@ isNaN() 与*math*包中 IsNaN() 实现完全相同,*sort*包之所以不使用
268270
func StringsAreSorted(a []string) bool
269271
func SearchStrings(a []string, x string) int
270272

273+
## 3.1.3 []interface 排序与查找
274+
275+
通过前面的内容我们可以知道,只要实现了 `sort.Interface` 接口,即可通过 sort 包内的函数完成排序,查找等操作。并且 sort 包已经帮我们把`[]int`,`[]float64`,`[]string` 三种类型都实现了该接口,我们可以方便的调用。但是这种用法对于其它数据类型的 slice 不友好,可能我们需要为大量的 struct 定义一个单独的 []struct 类型,再为其实现 `sort.Interface` 接口,类似这样:
276+
```go
277+
type Person struct {
278+
Name string
279+
Age int
280+
}
281+
type Persons []Person
282+
283+
func (p Persons) Len() int {
284+
panic("implement me")
285+
}
286+
287+
func (p Persons) Less(i, j int) bool {
288+
panic("implement me")
289+
}
290+
291+
func (p Persons) Swap(i, j int) {
292+
panic("implement me")
293+
}
294+
```
295+
296+
> 思考一个问题:为什么 sort 包可以完成 `[]int` 的排序,而不能完成 `[]struct` 的排序?
297+
298+
因为排序涉及到比较两个变量的值,而 struct 可能包含多个属性,程序并不知道你想以哪一个属性或哪几个属性作为衡量大小的标准。如果你能帮助程序完成比较,并将结果返回, sort 包内的方法就可以完成排序,判断,查找等。sort 包提供了以下函数:
299+
300+
```go
301+
func Slice(slice interface{}, less func(i, j int) bool)
302+
func SliceStable(slice interface{}, less func(i, j int) bool)
303+
func SliceIsSorted(slice interface{}, less func(i, j int) bool) bool
304+
func Search(n int, f func(int) bool) int
305+
```
306+
307+
308+
通过函数签名可以看到,排序相关的三个函数都接收 `[]interface`,并且需要传入一个比较函数,用于为程序比较两个变量的大小,因为函数签名和作用域的原因,这个函数只能是 `匿名函数`
309+
310+
**1. sort.Slice**
311+
312+
该函数完成 []interface 的排序,举个栗子:
313+
```go
314+
people := []struct {
315+
Name string
316+
Age int
317+
}{
318+
{"Gopher", 7},
319+
{"Alice", 55},
320+
{"Vera", 24},
321+
{"Bob", 75},
322+
}
323+
324+
sort.Slice(people, func(i, j int) bool { return people[i].Age < people[j].Age }) // 按年龄升序排序
325+
fmt.Println("Sort by age:", people)
326+
327+
// Output:
328+
/ /By age: [{Gopher 7} {Vera 24} {Alice 55} {Bob 75}]
329+
```
330+
331+
332+
**2. sort.SliceStable**
333+
334+
该函数完成 []interface 的稳定排序,举个栗子:
335+
```go
336+
people := []struct {
337+
Name string
338+
Age int
339+
}{
340+
{"Gopher", 7},
341+
{"Alice", 55},
342+
{"Vera", 24},
343+
{"Bob", 75},
344+
}
345+
346+
sort.Slice(people, func(i, j int) bool { return people[i].Age > people[j].Age }) // 按年龄降序排序
347+
fmt.Println("Sort by age:", people)
348+
349+
// Output:
350+
// By age: [{Bob 75} {Alice 55} {Vera 24} {Gopher 7}]
351+
```
352+
353+
**3. sort.SliceIsSorted**
354+
355+
该函数判断 []interface 是否为有序,举个栗子:
356+
```go
357+
people := []struct {
358+
Name string
359+
Age int
360+
}{
361+
{"Gopher", 7},
362+
{"Alice", 55},
363+
{"Vera", 24},
364+
{"Bob", 75},
365+
}
366+
367+
sort.Slice(people, func(i, j int) bool { return people[i].Age > people[j].Age }) // 按年龄降序排序
368+
fmt.Println("Sort by age:", people)
369+
370+
fmt.Println("Sorted:",sort.SliceIsSorted(people,func(i, j int) bool { return people[i].Age < people[j].Age }))
371+
// Output:
372+
// Sort by age: [{Bob 75} {Alice 55} {Vera 24} {Gopher 7}]
373+
// Sorted: false
374+
```
375+
376+
> sort 包没有为 []interface 提供反序函数,但是从 1 和 2 可以看出,我们传入的比较函数已经决定了排序结果是升序还是降序。
377+
378+
> 判断 slice 是否为有序,同样取决于我们传入的比较函数,从 3 可以看出,虽然 slice 已经按年龄降序排序,但我们在判断 slice 是否为有序时给的比较函数是判断其是否为升序有序,所以最终得到的结果为 false。
379+
380+
381+
**4. sort.Search**
382+
383+
该函数判断 []interface 是否存在指定元素,举个栗子:
384+
385+
- 升序 slice
386+
387+
> sort 包为 `[]int`,`[]float64`,`[]string` 提供的 Search 函数其实也是调用的该函数,因为该函数是使用的二分查找法,所以要求 slice 为升序排序状态。并且判断条件必须为 `>=`,这也是官方库提供的三个查找相关函数的的写法。
388+
389+
```go
390+
a := []int{2, 3, 4, 200, 100, 21, 234, 56}
391+
x := 21
392+
393+
sort.Slice(a, func(i, j int) bool { return a[i] < a[j] }) // 升序排序
394+
index := sort.Search(len(a), func(i int) bool { return a[i] >= x }) // 查找元素
395+
396+
if index < len(a) && a[index] == x {
397+
fmt.Printf("found %d at index %d in %v\n", x, index, a)
398+
} else {
399+
fmt.Printf("%d not found in %v,index:%d\n", x, a, index)
400+
}
401+
402+
// Output:
403+
// found 21 at index 3 in [2 3 4 21 56 100 200 234]
404+
```
405+
406+
- 降序 slice
407+
408+
如果 slice 是降序状态,而我们又不想将其变为升序,只需将判断条件由 `>=` 变更为 `<=` 即可。此处就不给代码了,有兴趣的同学可以自己去尝试一下。推荐采用升序排列及相应的判断条件,与官方函数保持风格一致。
409+
410+
**5. 性能测试**
411+
412+
```
413+
$ go test -bench=.
414+
goos: windows
415+
goarch: amd64
416+
pkg: lib/m_sort
417+
BenchmarkSortIntByInterfaceStable-12 30000 42365 ns/op
418+
BenchmarkSortIntByInterface-12 3000 409606 ns/op
419+
BenchmarkSortIntByFunctionStable-12 50000 38955 ns/op
420+
BenchmarkSortIntByFunction-12 50000000 35.0 ns/op
421+
BenchmarkSortFloatByInterfaceStable-12 20000 62481 ns/op
422+
BenchmarkSortFloatByInterface-12 3000 432123 ns/op
423+
BenchmarkSortFloatByFunctionStable-12 30000 40968 ns/op
424+
BenchmarkSortFloatByFunction-12 50000000 35.1 ns/op
425+
BenchmarkSortStringByInterfaceStable-12 20000 76405 ns/op
426+
BenchmarkSortStringByInterface-12 3000 570894 ns/op
427+
BenchmarkSortStringByFunctionStable-12 20000 72865 ns/op
428+
BenchmarkSortStringByFunction-12 50000000 39.5 ns/op
429+
BenchmarkSortPersonByInterfaceStable-12 30000 43716 ns/op
430+
BenchmarkSortPersonByInterface-12 3000 425633 ns/op
431+
BenchmarkSortPersonByFunctionStable-12 30000 40954 ns/op
432+
BenchmarkSortPersonByFunction-12 50000000 36.2 ns/op
433+
PASS
434+
ok lib/m_sort 28.921s
435+
```
436+
437+
- 测试分为 int、float64、string、struct 四种数据类型。每种数据类型进行 实现接口的稳定排序、实现接口的排序、调用函数的稳定排序、调用函数的排序 四种方式。
438+
439+
- 测试 slice 长度为 10000 的情况下,调用函数的排序与实现接口的排序差距最大,性能普遍差距在 10000 倍以上。
440+
441+
- 两种稳定排序性能差别不大,调用函数的稳定排序比实现接口的稳定排序稍快。
442+
443+
- 推荐使用直接调用函数的方式进行排序(sort.SliceSort、sort.SliceSortStable),并且这些函数接受参数类型为 []interface{} ,这意味着任意数据类型都可以调用该方法,只需传入一个比较函数即可,无需实现接口。
444+
271445
# 导航 #
272446

273447
- [第三章 数据结构与算法](/chapter03/03.0.md)

0 commit comments

Comments
 (0)