我想从切片中删除一些元素,并且https://github.com/golang/go/wiki/SliceTricks建议此切片操作:

a = append(a[:i], a[i+1:]...)

然后我在下面编码:

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)
}

使用go run hello.go时,它会惊慌:

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,带有goto语句:

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)
}

但不确定这是更好还是不好。

第三,使用len运算符从Remove 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]
}