玩轉 Go Slices 切片泛型庫
大家好,我是 陳明勇,一個熱愛技術,喜歡鑽研技術的程序員。
前言
在 Go
1.21.0 版本中,引入了 切片泛型庫,它提供了很多有用的函數,特別是在搜索、查找和排序等方面,爲我們開發者提供了諸多便利之處。而本文將會對 slices
庫提供的函數進行介紹,準備好了嗎,準備一杯你喜歡的咖啡或茶,隨着本文一探究竟吧。
slices
slices
庫包含的函數可以分爲以下類型:
-
搜索:通過二分查找算法查找指定元素。相關的函數有
BinarySearch
和BinarySearchFunc
-
裁剪:刪除切片中未使用的容量。相關的函數有
Clip
-
克隆:淺拷貝一個切片副本。相關的的函數有:
Clone
-
壓縮:將切片裏連續的相同元素替換爲一個元素。從而減少了切片的長度,相關的函數有:
Compact
和CompactFunc
-
大小比較:比較兩個切片的大小。相關的函數有
Compare
和CompareFunc
-
包含:判斷切片是否包含指定元素。相關的函數有:
Contains
和ContainsFunc
-
刪除:從切片中刪除一個或多個元素。相關的函數有
Delete
和DeleteFunc
-
等價比較:比較兩個切片是否相等。相關的函數有:
Equal
和EqualFunc
-
擴容:增加切片的容量。相關的函數有:
Grow
-
索引查找:查找指定元素在切片中的索引位置。相關的函數有:
Index
和IndexFunc
-
插入:往切片裏插入一組值。相關的函數有:
Insert
-
有序判斷:判斷切片是否按照升序排列。相關的函數有:
IsSorted
和IsSortedFunc
-
最大值:查找切片裏的最大元素。相關的函數有:
Max
和MaxFunc
-
最小值:查找切片裏的最小元素。相關的函數有:
Min
和MinFunc
-
替換:替換切片裏的元素。相關的函數有:
Replace
-
反轉:反轉切片的元素。相關的函數有:
Reverse
-
排序:對切片裏的元素進行升序排列。相關的函數有:
Sort
和SortFunc
以及SortStableFunc
搜索:BinarySearch 和 BinarySearchFunc
BinarySearch
BinarySearch
函數用於在有序的切片中 查找 目標元素,並返回其在切片中的 位置。該函數有兩個返回值,第一個是指定元素的下標索引,第二個是一個 bool
值,表示是否在切片中找到指定元素。
下面是使用該函數的一個例子:
// https://github.com/chenmingyong0423/blog/blob/master/tutorial-code/slices/binary_search/binary_search.go
package main
import (
"fmt"
"slices"
)
func main() {
scores := []int{70, 85, 90, 95, 98, 99, 100}
idx, b := slices.BinarySearch(scores, 80)
fmt.Println(idx, b)
idx, b = slices.BinarySearch(scores, 95)
fmt.Println(idx, b)
}
程序運行結果如下所示:
1 false
3 true
BinarySearchFunc
BinarySearchFunc
功能和 BinarySearch
類似,但它更加靈活,在它接收的參數裏,其中有一個是 cmp
比較函數,通過該函數我們可以爲任何的數據結構定義比較邏輯。
cmp
比較函數的介紹如下:
cmp func(E, T) int
-
E
:切片元素 -
T
:目標元素 -
返回值爲
int
類型
當 E
的位置在 T
之前,返回負數;當前 E
等於 T
時,應返回 0,當 E
的位置在 T
的後面時,返回正數。
下面是使用該函數的一個例子:
// https://github.com/chenmingyong0423/blog/blob/master/tutorial-code/slices/binary_search/binary_search_func.go
package main
import (
"cmp"
"fmt"
"slices"
)
func main() {
type User struct {
Name string
Age int
}
users := []User{
{"Aaron", 20},
{"Gopher", 24},
{"Harry", 18},
}
idx, b := slices.BinarySearchFunc(users, User{Name: "Gopher"}, func(src User, dst User) int {
return cmp.Compare(src.Name, dst.Name)
})
fmt.Println("Gopher:", idx, b)
}
在比較函數里,如果不是要實現特別複雜的比較,我們完全可以使用 cmp
包提供的 Compare
函數。
程序運行結果如下所示:
Gopher: 1 true
裁剪:Clip
Clip
函數用於刪除切片中未使用的容量,執行操作後,切片的長度 = 切片的容量。
// https://github.com/chenmingyong0423/blog/blob/master/tutorial-code/slices/clip/clip.go
package main
import (
"fmt"
"slices"
)
func main() {
s := make([]int, 0, 8)
s = append(s, 1, 2, 3, 4)
fmt.Printf("len: %d, cap: %d\n", len(s), cap(s))
s = slices.Clip(s)
fmt.Printf("len: %d, cap: %d\n", len(s), cap(s))
}
程序運行結果如下所示:
len: 4, cap: 8
len: 4, cap: 4
克隆:Clone
Clone
函數返回一個拷貝的切片副本,元素是賦值複製,因此是淺拷貝。
// https://github.com/chenmingyong0423/blog/blob/master/tutorial-code/slices/clone/clone.go
package main
import (
"fmt"
"slices"
)
func main() {
type User struct {
Name string
}
s := []*User{{Name: "Gopher"}}
copiedSlice := slices.Clone(s)
copiedSlice[0].Name = "陳明勇"
fmt.Println(s[0].Name == copiedSlice[0].Name) // true
}
由於是淺拷貝,修改副本切片裏的元素,原切片的元素也會更新。
壓縮:Compact 和 CompactFunc
Compact
Compact
函數會將切片裏連續的相同元素替換爲一個元素。
// https://github.com/chenmingyong0423/blog/blob/master/tutorial-code/slices/compact/compact.go
package main
import (
"fmt"
"slices"
)
func main() {
s := []int{1, 2, 2, 3, 3, 4, 5}
newSlice := slices.Compact(s)
fmt.Println(newSlice)
}
程序運行結果如下所示:
[1 2 3 4 5]
Compact
的原理是通過移動元素來合併重複項。儘管處理後的切片長度減少了,但其底層數組的實際值仍然包括被 “拋棄” 的元素,例如 [1, 2, 3, 4, 5, 4, 5]
。這些尾部的元素 [4, 5]
雖不在新切片中,但仍佔用內存。特別是當元素爲指針時,這些元素會阻止它們所引用的對象被垃圾回收。爲確保這些對象可以被回收,我們應該考慮將這些元素置爲 nil
。
CompactFunc
CompactFunc
和 Compact
函數功能類似,但它使用一個相等性函數來比較元素。
案例:相同元素合併爲一個,比較元素時,忽略大小寫
// https://github.com/chenmingyong0423/blog/blob/master/tutorial-code/slices/compact/compact_func.go
package main
import (
"fmt"
"slices"
"strings"
)
func main() {
names := []string{"cmy", "CmY", "Gopher", "GOPHER", "Jack"}
names = slices.CompactFunc(names, func(a, b string) bool {
return strings.ToLower(a) == strings.ToLower(b)
})
fmt.Println(names)
}
程序運行結果如下所示:
[cmy Gopher Jack]
大小比較:Compare 和 CompareFunc
Compare
Compare
函數是一個比較函數,內部使用 cmp
包的 Compare
函數對 s1
和 s2
的每對元素進行比較。元素按順序從索引 0 開始進行比較,直到有一對元素不相等。返回第一對不匹配元素的比較結果。如果兩個切片在某一個切片結束之前都保持相等,那麼長度較短的切片被認爲小於較長的切片。
如果 s1 == s2
,結果爲 0;如果 s1 < s2
,結果爲 -1;如果 s1 > s2
,結果爲 +1。
// https://github.com/chenmingyong0423/blog/blob/master/tutorial-code/slices/compare/compare.go
package main
import (
"fmt"
"slices"
)
func main() {
names := []string{"Aaron", "Bob", "Gopher"}
fmt.Println("相等: ", slices.Compare(names, []string{"Aaron", "Bob", "Gopher"}))
fmt.Println("G < F, 第一個的切片小於第二個的切片:", slices.Compare(names, []string{"Aaron", "Bob", "Frida"}))
fmt.Println("G > H, 第一個的切片大於第二個的切片:", slices.Compare(names, []string{"Aaron", "Bob", "Harry"}))
fmt.Println("3 > 2, 第一個的切片大於第二個的切片:", slices.Compare(names, []string{"Aaron", "Bob"}))
}
程序運行結果如下所示:
相等: 0
G < F, 第一個的切片小於第二個的切片: 1
G > H, 第一個的切片大於第二個的切片: -1
3 > 2, 第一個的切片大於第二個的切片: 1
CompareFunc
CompareFunc
和 Compare
函數的功能類似,但它對每對元素使用自定義的比較函數進行比較。比較函數在 BinarySearchFunc
小節裏已經介紹過,這裏就不多介紹。
案例:使用自定義的比較函數來比較兩個切片中的元素,此比較函數基於字符串的長度而不是字典順序。比較規則是:更短的字符串被認爲是較小的。
// https://github.com/chenmingyong0423/blog/blob/master/tutorial-code/slices/compare/compare_func.go
package main
import (
"fmt"
"slices"
)
func main() {
s1 := []string{"apple", "banana", "cherry"}
s2 := []string{"apple", "blueberry", "date"}
result := slices.CompareFunc(s1, s2, func(s string, s2 string) int {
iflen(s) < len(s2) {
return-1
} elseiflen(s) > len(s2) {
return1
}
return0
})
fmt.Println("第一個切片比第二個切片小:", result) // -1
}
程序運行結果如下所示:
第一個切片比第二個切片小: -1
包含:Contains 和 ContainsFunc
Contains
Contains
函數用於判斷切片裏是否包含指定元素。
// https://github.com/chenmingyong0423/blog/blob/master/tutorial-code/slices/contains/contains.go
package main
import (
"fmt"
"slices"
)
func main() {
numbers := []int{1, 5, -1, 3, 2}
hasNegativeOne := slices.Contains(numbers, -1)
fmt.Println("包含 -1:", hasNegativeOne)
}
程序運行結果如下所示:
包含 -1: true
ContainsFunc
ContainsFunc
和 Contains
函數功能類似,但它使用一個相等性函數來確定被包含的元素。
例如我們要在一個切片中判斷是否包含負數元素:
// https://github.com/chenmingyong0423/blog/blob/master/tutorial-code/slices/contains/contains_func.go
package main
import (
"fmt"
"slices"
)
func main() {
numbers := []int{1, 5, -1, 3, 2}
containNegative := slices.ContainsFunc(numbers, func(i int) bool {
return i < 0
})
fmt.Println("包含負數:", containNegative)
}
程序運行結果如下所示:
包含負數: true
刪除:Delete 和 DeleteFunc
Delete
Delete
函數的功能是從指定切片 s
中刪除指定範圍 s[i:j]
的元素,並返回新的的切片。
使用注意事項:
-
如果
s[i:j]
不是一個有效的範圍,則會panic
-
相比於逐個刪除的行爲,一次性刪除多個元素,效率會更好
-
由於該函數底層是通過索引範圍去構建新的切片,並沒有操作被 “拋棄” 的元素。它們仍然存在於底層的數組中。因此當元素爲指針時,這些元素會阻止它們所引用的對象被垃圾回收。爲確保這些對象可以被回收,我們應該考慮將這些元素置爲
nil
。
下面是使用該函數的一個例子:
// https://github.com/chenmingyong0423/blog/blob/master/tutorial-code/slices/delete/delete.go
package main
import (
"fmt"
"slices"
)
func main() {
numbers := []int{1, 2, 3, 4, 5}
newNumbers := slices.Delete(numbers, 1, 3)
fmt.Println(newNumbers)
}
程序運行結果如下所示:
[1 4 5]
刪除位置範圍 1 ~ 3
的元素,不包含位置 3。
DeleteFunc
DeleteFunc
和 Delete
函數功能類似,但它使用一個相等性函數來確定需要刪除的元素。
案例:從切片中刪除奇數元素
// https://github.com/chenmingyong0423/blog/blob/master/tutorial-code/slices/delete/delete_func.go
package main
import (
"fmt"
"slices"
)
func main() {
numbers := []int{1, 2, 3, 4, 5}
newNumbers := slices.DeleteFunc(numbers, func(i int) bool {
return i%2 != 0
})
fmt.Println(newNumbers)
}
程序運行結果如下所示:
[2 4]
等價比較:Equal 和 EqualFunc
Equal
Equal
函數用於比較兩個切片是否相等,要求切片的元素類型必須是可比較 (comparable
) 的。 其工作原理如下:
首先檢查兩個切片的長度,如果長度不同,則直接返回 false
,表示這兩個切片不相等。如果長度相同,函數會逐個比較元素,按照遞增的順序進行比較。需要注意的是,對於浮點數,函數會忽略 NaN
值,不將其視爲相等。
// https://github.com/chenmingyong0423/blog/blob/master/tutorial-code/slices/equal/equal.go
package main
import (
"fmt"
"slices"
)
func main() {
numbers := []int{0, 1, 2}
fmt.Println(slices.Equal(numbers, []int{0, 1, 2}))
fmt.Println(slices.Equal(numbers, []int{3}))
}
程序運行結果如下所示:
true
false
EqualFunc
EqualFunc
和 Equal
函數功能類似,但它使用一個相等性函數來比較元素。
案例:忽略大小寫比較
// https://github.com/chenmingyong0423/blog/blob/master/tutorial-code/slices/equal/equal_func.go
package main
import (
"fmt"
"slices"
"strings"
)
func main() {
names := []string{"cmy", "Gopher"}
equal := slices.EqualFunc(names, []string{"CMY", "GOPHER"}, func(s string, s2 string) bool {
return strings.ToLower(s) == strings.ToLower(s2)
})
fmt.Println(equal)
}
程序運行結果如下所示:
true
擴容:Grow
Grow
函數會根據需要增加切片的容量,以確保可以容納另外 n
個元素。在調用 Grow(n)
後,至少可以追加 n
個元素到切片中而無需再次分配內存。如果 n
爲負數或者需要分配的內存太大,Grow
會引發異常。
// https://github.com/chenmingyong0423/blog/blob/master/tutorial-code/slices/grow/grow.go
package main
import (
"fmt"
"slices"
)
func main() {
s := make([]int, 4, 5)
fmt.Printf("len=%d, cap=%d\n", len(s), cap(s))
grow := slices.Grow(s, 4)
fmt.Printf("len=%d, cap=%d\n", len(grow), cap(grow))
}
程序運行結果如下所示:
len=4, cap=5
len=4, cap=10
在調用 Grow
函數擴容之前,切片 s
可用容量只有 1,在擴容之後,可用容量爲 6,可確保能至少能容納 4 個元素。
索引查找:Index 和 IndexFunc
Index
Index
函數返回指定元素在切片裏第一次出現的下標索引值,如果元素不存在,則返回 -1 。
// https://github.com/chenmingyong0423/blog/blob/master/tutorial-code/slices/index/index.go
package main
import (
"fmt"
"slices"
)
func main() {
numbers := []int{0, 1, 2}
fmt.Println("找到元素位置:", slices.Index(numbers, 2))
fmt.Println("未找到元素位置:", slices.Index(numbers, 3))
}
程序運行結果如下所示:
找到元素位置: 2
未找到元素位置: -1
IndexFunc
IndexFunc
和 Index
函數功能類似,但它使用一個相等性函數來比較元素。
// https://github.com/chenmingyong0423/blog/blob/master/tutorial-code/slices/index/index_func.go
package main
import (
"fmt"
"slices"
)
func main() {
numbers := []int{1, 5, -1, 3, 2}
idx := slices.IndexFunc(numbers, func(i int) bool {
return i < 0
})
fmt.Println("負數的索引:", idx)
}
程序運行結果如下所示:
負數的索引: 2
插入:Insert
Insert
函數用於在一個切片 s
中的指定位置 i
處插入一組值 v...
,然後返回修改後的切片。如果指定的索引 i
越界了,則會發生錯誤。
// https://github.com/chenmingyong0423/blog/blob/master/tutorial-code/slices/insert/insert.go
package main
import (
"fmt"
"slices"
)
func main() {
numbers := []int{1, 3, 4}
numbers = slices.Insert(numbers, 1, 2)
numbers = slices.Insert(numbers, len(numbers), 5, 6)
fmt.Println(numbers)
}
程序運行結果如下所示:
[1 2 3 4 5 6]
有序判斷:IsSorted 和 IsSortedFunc
IsSorted
IsSorted
函數用於判斷切片是按升序排列。
// https://github.com/chenmingyong0423/blog/blob/master/tutorial-code/slices/is_sorted/is_sorted.go
package main
import (
"fmt"
"slices"
)
func main() {
fmt.Println("是升序排列:", slices.IsSorted([]int{1, 2, 3, 4, 5}))
fmt.Println("不是升序排列:", slices.IsSorted([]int{1, 2, 3, 5, 4}))
}
程序運行結果如下所示:
是升序排列: true
不是升序排列: false
IsSortedFunc
IsSortedFunc
和 IsSorted
函數功能類似,但它對每對元素使用自定義的比較函數進行比較。比較函數在 BinarySearchFunc
小節裏已經介紹過,這裏就不多介紹。
// https://github.com/chenmingyong0423/blog/blob/master/tutorial-code/slices/is_sorted/is_sorted_func.go
package main
import (
"cmp"
"fmt"
"slices"
"strings"
)
func main() {
names := []string{"aaron", "Bob", "GOPHER"}
isSortedInsensitive := slices.IsSortedFunc(names, func(a, b string) int {
return cmp.Compare(strings.ToLower(a), strings.ToLower(b))
})
fmt.Println("是升序排列:", isSortedInsensitive)
fmt.Println("不是升序排列:", slices.IsSorted(names))
}
程序運行結果如下所示:
是升序排列: true
不是升序排列: false
最大值:Max 和 MaxFunc
Max
Max
函數返回切片中最大的元素,如果切片爲空,則 panic
。對於浮點數類型,如果切片中包含 NaN
(非數字)值,那麼結果將是 NaN
。 NaN
是一種特殊的浮點數值,表示不是一個數字或無效數字。如果切片包含 NaN
,那麼最大值也將是 NaN
,這是因爲 NaN
不可比較大小。
// https://github.com/chenmingyong0423/blog/blob/master/tutorial-code/slices/max/max.go
package main
import (
"fmt"
"slices"
)
func main() {
fmt.Println("最大的元素:", slices.Max([]int{1, 2, 5, 3, 4}))
}
程序運行結果如下所示:
最大的元素: 5
MaxFunc
MaxFunc
和 Max
函數功能類似,但它使用一個相等性函數來比較元素。
// https://github.com/chenmingyong0423/blog/blob/master/tutorial-code/slices/max/max_func.go
package main
import (
"cmp"
"fmt"
"slices"
)
func main() {
type User struct {
Name string
Age int
}
users := []User{
{"Aaron", 20},
{"Gopher", 24},
{"Harry", 18},
}
maxUser := slices.MaxFunc(users, func(a, b User) int {
return cmp.Compare(a.Age, b.Age)
})
fmt.Println("最大的元素:", maxUser)
}
程序運行結果如下所示:
最大的元素: {Gopher 24}
最小值:Min 和 MinFunc
Min
Min
函數返回切片中最小的元素,如果切片爲空,則 panic
。對於浮點數類型,如果切片中包含 NaN
(非數字)值,那麼結果將是 NaN
。 NaN
是一種特殊的浮點數值,表示不是一個數字或無效數字。如果切片包含 NaN
,那麼最小值也將是 NaN
,這是因爲 NaN
不可比較大小。
// https://github.com/chenmingyong0423/blog/blob/master/tutorial-code/slices/min/min.go
package main
import (
"fmt"
"slices"
)
func main() {
fmt.Println("最小的元素:", slices.Max([]int{1, 2, 5, 3, 4}))
}
程序運行結果如下所示:
最小的元素: 1
MaxFunc
MaxFunc
和 Max
函數功能類似,但它使用一個相等性函數來比較元素。
// https://github.com/chenmingyong0423/blog/blob/master/tutorial-code/slices/min/min_func.go
package main
import (
"cmp"
"fmt"
"slices"
)
func main() {
type User struct {
Name string
Age int
}
users := []User{
{"Aaron", 20},
{"Gopher", 24},
{"Harry", 18},
}
maxUser := slices.MaxFunc(users, func(a, b User) int {
return cmp.Compare(a.Age, b.Age)
})
fmt.Println("最小的元素:", maxUser)
}
程序運行結果如下所示:
最小的元素: {Harry 18}
替換:Replace
Replace
函數用於將切片s
中的元素 s[i:j]
替換爲給定的元素組 v
,然後返回修改後的切片。如果 s[i:j]
不是 s 的有效切片範圍,函數會引發 panic
。
// https://github.com/chenmingyong0423/blog/blob/master/tutorial-code/slices/replace/replace.go
package main
import (
"fmt"
"slices"
)
func main() {
numbers := []int{1, 0, 0, 5}
numbers = slices.Replace(numbers, 1, 3, 2, 3, 4)
fmt.Println(numbers)
}
程序運行結果如下所示:
[1 2 3 4 5]
反轉:Reverse
Reverse
函數用於反轉切片中的元素,在給定切片裏將元素的順序顛倒過來,而不會創建新的切片。
// https://github.com/chenmingyong0423/blog/blob/master/tutorial-code/slices/reverse/reverse.go
package main
import (
"fmt"
"slices"
)
func main() {
numbers := []int{1, 2, 3, 4}
slices.Reverse(numbers)
fmt.Println(numbers)
}
程序運行結果如下所示:
[4 3 2 1]
排序:Sort 和 SortFunc 以及 SortStableFunc
Sort
Sort
函數用於對切片中的元素進行升序排序。當對浮點數進行排序時,NaN
值會被排在其他值的前面。這意味着在排序浮點數時,NaN
值會被視爲最小值,排在結果的最前面。
// https://github.com/chenmingyong0423/blog/blob/master/tutorial-code/slices/sort/sort.go
package main
import (
"fmt"
"math"
"slices"
)
func main() {
ints := []int{1, 2, 5, 3, 4}
slices.Sort(ints)
floats := []float64{2.0, 3.0, math.NaN(), 1.0}
slices.Sort(floats)
fmt.Println(ints)
fmt.Println(floats)
}
程序運行結果如下所示:
[1 2 3 4 5]
[NaN 1 2 3]
SortFunc
SortFunc
和 Sort
函數功能類似,但它對每對元素使用自定義的比較函數進行比較。比較函數在 BinarySearchFunc
小節裏已經介紹過,這裏就不多介紹。
// https://github.com/chenmingyong0423/blog/blob/master/tutorial-code/slices/sort/sort_func.go
package main
import (
"cmp"
"fmt"
"slices"
"strings"
)
func main() {
names := []string{"Bob", "Aaron", "GOPHER"}
slices.SortFunc(names, func(a, b string) int {
return cmp.Compare(strings.ToLower(a), strings.ToLower(b))
})
fmt.Println(names)
}
程序運行結果如下所示:
[1 2 3 4 5]
[NaN 1 2 3]
SortStableFunc
SortStableFunc
和 SortFunc
函數功能類似,但它進行的是穩定排序,它會保持相等元素的原始順序。
定排序意味着當有多個相等的元素時,它們的相對順序在排序後會保持不變。例如,如果有兩個元素 A
和 B
,它們的值相等,且在原始切片中 A
出現在 B
之前,那麼在排序後 A 仍然會出現在 B
之前,不會改變它們的相對位置。
// https://github.com/chenmingyong0423/blog/blob/master/tutorial-code/slices/sort/sort_stable_func.go
package main
import (
"cmp"
"fmt"
"slices"
)
func main() {
type User struct {
Name string
Age int
}
users := []User{
{"Aaron", 20},
{"Gopher", 16},
{"Harry", 16},
{"Burt", 18},
}
slices.SortStableFunc(users, func(a, b User) int {
return cmp.Compare(a.Age, b.Age)
})
fmt.Println(users)
}
程序運行結果如下所示:
[{Gopher 16} {Harry 16} {Burt 18} {Aaron 20}]
排序之前,Harry
在 Gopher
後面,排序之後,也是同樣的相對位置。
小結
本文全面介紹了 Go slices
庫的所有函數,並着重指出了使用某些函數時的注意事項,通過閱讀本文,相信你將能夠熟練掌握如何使用 Go Slices
庫。
本文中涉及到的相關代碼,都已上傳至:github.com/chenmingyong0423/blog/tree/master/tutorial-code/slices
”
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/bSgA-nIpzZmfNN7SKgOzjg