Golang 任意类型切片的增删改查

在这里插入图片描述

大咖好呀,我是恋喵大鲤鱼。

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

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值