在迭代切片恐慌时Golang删除元素

时间:2016-07-15 03:57:35

标签: go slice

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

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("%v\n", 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("%v\n", slice)
}

它有效,但迭代次数太多

第二,使用共享相同后备阵列的另一个切片:

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\n", slice)
}

但不确定这个是否更好。

第3名,来自Remove elements in slicelen运营商:

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\n", 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 个答案:

答案 0 :(得分: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循环。

working playground example

答案 1 :(得分:1)

虽然这是小片的好答案:

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

对于第一个元素(对于大切片)最小化内存写入,您可以使用:

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