大咖好呀,我是恋喵大鲤鱼。
0.切片简介
slice 名为切片,是 Go 中的可变长数组,是对底层数组的封装和引用。
切片指向一个底层数组,并且包含长度和容量信息。未初始化切片的值为 nil。
作用于切片的内建函数主要有四个,分别是 make、len、cap 和 append。make 用于创建切片,len 用于获取切片的长度,cap 用于获取切片的容量,append 用于向切片追加元素。
package main
import "fmt"
func main() {
//创建切片,make([]T, length, capacity)
fib := make([]int, 0, 10)
fmt.Println("len(fib)=", len(fib))
fmt.Println("cap(fib)=", cap(fib))
fib = append(fib, []int{1, 1, 2, 3, 5, 8, 13}...)
fmt.Println("fib=", fib)
}
输出结果:
len(fib)= 0
cap(fib)= 10
fib= [1 1 2 3 5 8 13]
1.增加元素
利用 Golang 的反射能力,我们可以使用 any 接收任意类型的切片,然后使用 reflect 包提供的相关函数向任意类型的切片添加元素。
注意,不要使用 []any 表示任意类型的切片,它表示的是存放任意类型元素的切片,但不能表示任意类型的切片。
// Insert inserts the values v... into s at index i and returns the result slice.
// Unlike the standard library function Insert, it does not modify the original slice.
func Insert[S ~[]E, E any](s S, i int, v ...E) S {
r := make(S, len(s)+len(v))
copy(r, s[:i])
copy(r[i:], v)
copy(r[i+len(v):], s[i:])
return r
}
func main() {
fib := []int{1, 2, 3, 5, 8}
// 切片头部插入元素
o, _ := Insert(fib, 0, 1)
fmt.Println("fib =", o)
}
编译运行输出:
fib = [1 1 2 3 5 8]
当然,我们也可以使用反射实现。但是因为反射的性能略低于泛型,所以建议使用泛型。
// InsertRef inserts elements to slice in the specified index implemented by reflect.
// Unlike the standard library function Insert, the original slice will not be modified.
func InsertRef(a any, i int, v ...any) any {
val := reflect.ValueOf(a)
t := reflect.TypeOf(a)
r := reflect.MakeSlice(t, 0, val.Len()+len(v))
r = reflect.AppendSlice(r, val.Slice(0, i))
for i := range v {
r = reflect.Append(r, reflect.ValueOf(v[i]))
}
r = reflect.AppendSlice(r, val.Slice(i, val.Len()))
return r.Interface()
}
2.删除元素
比如可以通过指定元素下标进行删除。
// Delete removes the specified indexes elements from the slice.
// Unlike the standard library function Delete, the original slice will not be modified.
func Delete[S ~[]E, E any](s S, indexes ...int) S {
// Convert the indexes to map set.
m := make(map[int]struct{})
for _, i := range indexes {
m[i] = struct{}{}
}
// Filter out specified index elements.
r := make(S, 0, len(s))
for i := range s {
if _, ok := m[i]; !ok {
r = append(r, s[i])
}
}
return r
}
func main() {
fib := []int{1, 1, 2, 3, 5, 8}
r1 := Delete(fib, len(fib) - 1) //删除切片最后一个元素
fmt.Println("r1 =", r1)
s := []string{"foo", "bar", "baz", "qux"}
r2 := Delete(s, 1, 2) //删除多个元素
fmt.Println("r2 =", r2)
}
编译运行输出:
f1 = [1 1 2 3 5]
r2 = [foo qux]
当然,我们也可以使用反射实现。但是因为反射的性能略低于泛型,所以建议使用泛型。
// DeleteRef removes the elements specified by indexes from the slice.
// Note that the original slice will not be modified.
func DeleteRef(a any, indexes ...int) any {
// Convert the indexes to map set.
m := make(map[int]struct{})
for _, i := range indexes {
m[i] = struct{}{}
}
// Filter out specified index elements.
v := reflect.ValueOf(a)
t := reflect.MakeSlice(reflect.TypeOf(a), 0, v.Len())
for i := 0; i < v.Len(); i++ {
if _, ok := m[i]; !ok {
t = reflect.Append(t, v.Index(i))
}
}
return t.Interface()
}
3.修改元素
比如指定下标范围,替换为指定的元素。
// Replace replaces the elements s[i:j] by the given v.
// The resulting slice len is equal to the original slice.
// Replace panics if s[i:j] is not a valid slice of s.
// Unlike the standard library function Replace, the original slice will not be modified.
func Replace[S ~[]E, E any](s S, i, j int, v ...E) S {
_ = s[i:j] // verify that i:j is a valid subslice
r := make(S, len(s))
copy(r, s[:i])
for k := i; k < j; k++ {
r[k] = v[k-i]
}
copy(r[j:], s[j:])
return r
}
func main() {
r1 := Replace([]int{0, 1, 2, 3, 5, 8}, 0, 1, 0)
fmt.Println("r1 =", r1)
r2 := Replace([]string{"fooo", "barr", "baz", "qux"}, 0, 2, "foo", "bar")
fmt.Println("r2 =", r2)
}
运行输出:
r1 = [0 1 2 3 5 8]
r2 = [foo bar baz qux]
当然,也可以使用反射实现。但是因为反射的性能略低于泛型,所以建议使用泛型。
// ReplaceRef modifies the specified index elements of slice implemented by reflect.
// The resulting slice len is equal to the original slice.
// ReplaceRef panics if s[i:j] is not a valid slice of s.
// Note that the original slice will not be modified.
func ReplaceRef(a any, i, j int, v ...any) any {
val := reflect.ValueOf(a)
r := reflect.MakeSlice(reflect.TypeOf(a), val.Len(), val.Len())
reflect.Copy(r.Slice(0, i), val.Slice(0, i))
for k := i; k < j; k++ {
r.Index(k).Set(reflect.ValueOf(v[k-i]))
}
reflect.Copy(r.Slice(j, val.Len()), val.Slice(j, val.Len()))
return r.Interface()
}
4.查找元素下标
可以根据元素查找元素的所有下标。
// Indexes returns the specified element all indexes.
// Indexes implemented by generics has a better performance than Indexes implemented by generics
// and be recommended to use.
func Indices[E comparable](s []E, v E) []int {
var indices []int
for i := range s {
if v == s[i] {
indices = append(indices, i)
}
}
return indices
}
// IndexesFunc returns the specified element all indexes satisfying f(s[i]).
func IndexesFunc[E any](s []E, f func(E) bool) []int {
var indices []int
for i := range s {
if f(s[i]) {
indices = append(indices, i)
}
}
return indices
}
func main() {
r1 := Indices([]int{1, 1, 2, 3, 5, 8}, 1)
fmt.Println("r1 =", r1)
r2 := Indices([]string{"foo", "bar", "bar", "baz"}, "bar")
fmt.Println("r2 =", r2)
}
编译输出结果:
r1 = [0 1]
r2 = [1 2]
当然,也可以使用反射实现。但是因为反射的性能略低于泛型,所以建议使用泛型。
// IndexesRef returns the specified element all indexes from a slice or array with returned error.
func IndexesRef(a any, value any) []int {
v := reflect.ValueOf(a)
var indexes []int
for i := 0; i < v.Len(); i++ {
if v.Index(i).Interface() == value {
indexes = append(indexes, i)
}
}
return indexes
}
5.dablelv/cyan
以上代码已放置实用函数库 dablelv/cyan,可 import 直接使用,欢迎大家 star 和 pr。
import (
"github.com/dablelv/cyan/slice"
)
fib := []int{1, 1, 2, 3, 5, 8}
r := slice.Insert(fib, 6, 13) // [1 1 2 3 5 8 13]
r := slice.Delete(fib, 0) // [1 2 3 5 8]
r := slice.Replace(fib, 4,6,10, 16) // [1 1 2 3 10 16]
r := slice.Indices(fib, 1) // [0 1]
6.Go 1.18 泛型
自 Go 1.18 开始,Go 引入泛型,并在实验包 golang.org/x/exp/slices 提供了对任意元素类型切片的基础操作。
// Insert 插入。
func Insert[S ~[]E, E any](s S, i int, v ...E) S
// Delete 删除。
func Delete[S ~[]E, E any](s S, i, j int) S
// Replace 修改。
func Replace[S ~[]E, E any](s S, i, j int, v ...E) S
// Index 查找。
func Index[E comparable](s []E, v E) int
// IndexFunc 返回满足 f(s[i]) 的第一个索引 i,如果不满足则返回 -1。
func IndexFunc[E any](s []E, f func(E) bool) int
如果标准库提供的函数能够满足大家的需要,建议大家使用官方提供的泛型函数来完成任意类型切片的增删改查。
7.小结
任意类型切片的增删改查实现主要有两种方式,Go 1.18 之前使用反射,Go 1.18 及其之后使用泛型。
本文介绍了任意类型切片的增删改查,均是对标准库的补充。一个较大的区别是文中实现的切片增删改查均不会修改原切片,而标准库则会。并提供了反射的实现版本,当然由于反射有运行时的额外开销,性能略低于泛型版本,所以建议使用泛型版本。
如果您喜欢这篇文章,欢迎关注我的微信公众号“恋喵大鲤鱼”了解最新精彩内容。
参考文献
Golang Package reflect
【Golang】slice删除元素的性能对比
【golang】传递任意类型的切片
golang实现通过索引删除任意类型的slice元素
Set slice index using reflect in Go