我想从切片中删除一些元素,并且https://github.com/golang/go/wiki/SliceTricks建议此切片操作:
然后我在下面编码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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("%v ", slice) } |
使用
1 2 3 4 5 6 7 8 | 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,带有
1 2 3 4 5 6 7 8 9 10 11 12 | 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("%v ", slice) } |
它有效,但是迭代太多
2,使用共享相同支持数组的另一个切片:
1 2 3 4 5 6 7 8 9 10 11 12 | 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("%v ", slice) } |
但不确定这是更好还是不好。
第三,使用
1 2 3 4 5 6 7 8 9 10 11 | 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("%v ", slice) } |
我现在应该选哪一个?
与基准:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | 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 |
似乎删除一个循环中的所有元素更好
遍历要保留的切片复制元素。
1 2 3 4 5 6 7 8 | k := 0 for _, n := range slice { if n%3 != 0 { // filter slice[k] = n k++ } } slice = slice[:k] // set slice len to remaining elements |
在删除单个元素的情况下,切片技巧非常有用。 如果有可能删除多个元素,请使用上面的for循环。
工作操场的例子
- 除了这个答案之外,像这样遍历整个数组并填充一个新数组会更有效。 如果您在此处使用简单的基元,那么通过预测实际上会获得硬件性能的提高。 阅读有关编码的"机械同情",尤其是在Go中,以利用CPU算法。 虽然您的基准测试在9个数字上可能不会显示出太大的收益,但是尝试几百万个就能看到收益。
- 你救了我
虽然这是小片的好答案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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] } |
为了最大程度地减少前几个元素的内存写入(对于大片段),可以使用以下命令:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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] } |
并且如果您需要新切片或保留旧切片:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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] } |