删除一个切片的一些元素,https://github.com/golang/go/wiki/SliceTricks告知切片操作:Golang遍历切片恐慌时删除元素
a = append(a[:i], a[i+1:]...)
然后我下面的编码:
package main import ( "fmt" ) func main() { slice := []int{1, 2, 3, 4, 5, 6, 7, 8, 9} for i, value := range slice { if value%3 == 0 { // remove 3, 6, 9 slice = append(slice[:i], slice[i+1:]...) } } fmt.Printf("%vn", slice) }
与go run hello.go
,它恐慌:
panic: runtime error: slice bounds out of range goroutine 1 [running]: panic(0x4ef680, 0xc082002040) D:/Go/src/runtime/panic.go:464 +0x3f4 main.main() E:/Code/go/test/slice.go:11 +0x395 exit status 2
我该如何更改此代码才能正确使用?
想到的有几下几种方法
1、使用goto
和标签
func main() { slice := []int{1, 2, 3, 4, 5, 6, 7, 8, 9} Label: for i, n := range slice { if n%3 == 0 { slice = append(slice[:i], slice[i+1:]...) goto Label } } fmt.Printf("%vn", slice) }
它的工作原理很简单,就是不停的迭代,每次删除一个元素后,都需要从切片的开头重新迭代,太过于效率低下了,特别是当一个切片很大的情况下。
2,使用另一个变量临时存储想要的元素
func main() { slice := []int{1, 2, 3, 4, 5, 6, 7, 8, 9} dest := slice[:0] for _, n := range slice { if n%3 != 0 { // filter dest = append(dest, n) } } slice = dest fmt.Printf("%vn", slice) }
这种方法有些浪费内存,一是申请变量占用内存,另外切片扩大时,底层会重新申请内存空间,并将数据迁移过去。
3,从Remove elements in slice,与len
操作:
func main() { slice := []int{1, 2, 3, 4, 5, 6, 7, 8, 9} for i := 0; i < len(slice); i++ { if slice[i]%3 == 0 { slice = append(slice[:i], slice[i+1:]...) i-- // should I decrease index here? } } fmt.Printf("%vn", slice) }
要选哪一个,这里看一下基准测试
与基准:
func BenchmarkRemoveSliceElementsBySlice(b *testing.B) { for i := 0; i < b.N; i++ { slice := []int{1, 2, 3, 4, 5, 6, 7, 8, 9} dest := slice[:0] for _, n := range slice { if n%3 != 0 { dest = append(dest, n) } } } } func BenchmarkRemoveSliceElementByLen(b *testing.B) { for i := 0; i < b.N; i++ { slice := []int{1, 2, 3, 4, 5, 6, 7, 8, 9} for i := 0; i < len(slice); i++ { if slice[i]%3 == 0 { slice = append(slice[:i], slice[i+1:]...) } } } } $ go test -v -bench=".*" testing: warning: no tests to run PASS BenchmarkRemoveSliceElementsBySlice-4 50000000 26.6 ns/op BenchmarkRemoveSliceElementByLen-4 50000000 32.0 ns/op
从结果上看来使用第2种方法好像好一些的。还哪更好的解决办法没有了呢,网友给出了一种更合理的解决方案,也是使用迭代。
最佳方案:
package main import "fmt" func main() { slice := []int{1, 2, 3, 4, 5, 6, 7, 8, 9} k := 0 for _, n := range slice { if n%3 != 0 { // filter slice[k] = n k++ } } slice = slice[:k] fmt.Println(slice) //[1 2 4 5 7 8] }
实现原理就是将要保留的数据向左边存储,参考:https://play.golang.org/p/eMZltc_gEB,不得不说这个方法真是让人脑洞大开。
package main import "fmt" func main() { slice := []int{1, 2, 3, 4, 5, 6, 7, 8, 9} k := 0 for i, n := range slice { if n%3 != 0 { // filter if i != k { slice[k] = n } k++ } } slice = slice[:k] fmt.Println(slice) //[1 2 4 5 7 8] }
如果您需要新片或保留旧切片:
package main import "fmt" func main() { slice := []int{1, 2, 3, 4, 5, 6, 7, 8, 9} s2 := make([]int, len(slice)) k := 0 for _, n := range slice { if n%3 != 0 { // filter s2[k] = n k++ } } s2 = s2[:k] fmt.Println(s2) //[1 2 4 5 7 8] }