@@ -42,7 +42,7 @@ Sort() 方法惟一的参数就是待排序的数据集合。
42
42
43
43
下面是一个使用 sort 包对学生成绩排序的示例:
44
44
45
- ``` golang
45
+ ``` go
46
46
package main
47
47
48
48
import (
@@ -101,7 +101,7 @@ func main() {
101
101
[{leao 90} {hikerell 91} {alan 95} {acmfly 96}]
102
102
103
103
该示例实现的是升序排序,如果要得到降序排序结果,其实只要修改 Less() 函数:
104
- ``` golang
104
+ ``` go
105
105
// Less(): 成绩降序排序 , 只将小于号修改为大于号
106
106
func (s StuScores ) Less (i , j int ) bool {
107
107
return s[i].score > s[j].score
@@ -112,7 +112,7 @@ func (s StuScores) Less(i, j int) bool {
112
112
func Reverse(data Interface) Interface
113
113
114
114
我们可以看到 Reverse() 返回的一个 sort.Interface 接口类型,整个 Reverse() 的内部实现比较有趣:
115
- ``` golang
115
+ ``` go
116
116
// 定义了一个 reverse 结构类型,嵌入 Interface 接口
117
117
type reverse struct {
118
118
Interface
@@ -130,7 +130,7 @@ func (s StuScores) Less(i, j int) bool {
130
130
}
131
131
```
132
132
了解内部原理后,可以在学生成绩排序示例中使用 Reverse() 来实现成绩升序排序:
133
- ``` golang
133
+ ``` go
134
134
sort.Sort (sort.Reverse (stus))
135
135
fmt.Println (stus)
136
136
```
@@ -145,7 +145,7 @@ func (s StuScores) Less(i, j int) bool {
145
145
146
146
Search() 函数一个常用的使用方式是搜索元素 x 是否在已经升序排好的切片 s 中:
147
147
148
- ``` golang
148
+ ``` go
149
149
x := 11
150
150
s := []int {3 , 6 , 8 , 11 , 45 } // 注意已经升序排序
151
151
pos := sort.Search (len (s), func (i int ) bool { return s[i] >= x })
@@ -158,7 +158,7 @@ Search() 函数一个常用的使用方式是搜索元素 x 是否在已经升
158
158
159
159
官方文档还给出了一个猜数字的小程序:
160
160
161
- ``` golang
161
+ ``` go
162
162
func GuessingGame () {
163
163
var s string
164
164
fmt.Printf (" Pick an integer from 0 to 100.\n " )
@@ -181,7 +181,7 @@ func GuessingGame() {
181
181
182
182
* sort* 包定义了一个 IntSlice 类型,并且实现了 sort.Interface 接口:
183
183
184
- ``` golang
184
+ ``` go
185
185
type IntSlice []int
186
186
func (p IntSlice ) Len () int { return len (p) }
187
187
func (p IntSlice ) Less (i, j int) bool { return p[i] < p[j] }
@@ -192,34 +192,34 @@ func GuessingGame() {
192
192
func (p IntSlice ) Search (x int) int { return SearchInts (p, x) }
193
193
```
194
194
并且提供的 sort.Ints() 方法使用了该 IntSlice 类型:
195
- ``` goalng
195
+ ``` go
196
196
func Ints (a []int ) { Sort (IntSlice (a)) }
197
197
```
198
198
199
199
所以,对[ ] int 切片排序更常使用 sort.Ints(),而不是直接使用 IntSlice 类型:
200
200
201
- ``` golang
201
+ ``` go
202
202
s := []int {5 , 2 , 6 , 3 , 1 , 4 } // 未排序的切片数据
203
203
sort.Ints (s)
204
204
fmt.Println (s) // 将会输出[1 2 3 4 5 6]
205
205
```
206
206
如果要使用降序排序,显然要用前面提到的 Reverse() 方法:
207
207
208
- ``` golang
208
+ ``` go
209
209
s := []int {5 , 2 , 6 , 3 , 1 , 4 } // 未排序的切片数据
210
210
sort.Sort (sort.Reverse (sort.IntSlice (s)))
211
211
fmt.Println (s) // 将会输出[6 5 4 3 2 1]
212
212
```
213
213
214
214
如果要查找整数 x 在切片 a 中的位置,相对于前面提到的 Search() 方法,* sort* 包提供了 SearchInts():
215
215
216
- ``` golang
216
+ ``` go
217
217
func SearchInts (a []int , x int ) int
218
218
```
219
219
注意,SearchInts() 的使用条件为:**切片 a 已经升序排序**
220
220
以下是一个错误使用的例子:
221
221
222
- ```golang
222
+ ```go
223
223
s := []int{5 , 2 , 6 , 3 , 1 , 4 } // 未排序的切片数据
224
224
fmt.Println (sort.SearchInts (s, 2 )) // 将会输出 0 而不是 1
225
225
```
@@ -228,7 +228,7 @@ func GuessingGame() {
228
228
229
229
实现与 Ints 类似,只看一下其内部实现:
230
230
231
- ``` golang
231
+ ``` go
232
232
type Float64Slice []float64
233
233
234
234
func (p Float64Slice ) Len () int { return len (p) }
@@ -239,9 +239,11 @@ func GuessingGame() {
239
239
```
240
240
与 Sort()、IsSorted()、Search() 相对应的三个方法:
241
241
242
+ ``` go
242
243
func Float64s (a []float64 )
243
244
func Float64sAreSorted(a []float64) bool
244
245
func SearchFloat64s(a []float64, x float64) int
246
+ ```
245
247
246
248
要说明一下的是,在上面 Float64Slice 类型定义的 Less 方法中,有一个内部函数 isNaN()。
247
249
isNaN() 与*math*包中 IsNaN() 实现完全相同,*sort*包之所以不使用 math.IsNaN(),完全是基于包依赖性的考虑,应当看到,*sort*包的实现不依赖与其他任何包。
@@ -252,7 +254,7 @@ isNaN() 与*math*包中 IsNaN() 实现完全相同,*sort*包之所以不使用
252
254
253
255
实现与 Ints 类似,只看一下其内部实现:
254
256
255
- ``` golang
257
+ ```go
256
258
type StringSlice []string
257
259
258
260
func (p StringSlice) Len() int { return len (p) }
@@ -268,6 +270,178 @@ isNaN() 与*math*包中 IsNaN() 实现完全相同,*sort*包之所以不使用
268
270
func StringsAreSorted(a []string) bool
269
271
func SearchStrings(a []string, x string) int
270
272
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
+
271
445
# 导航 #
272
446
273
447
- [ 第三章 数据结构与算法] ( /chapter03/03.0.md )
0 commit comments