Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Golang remove elements when iterating over slice panics

Tags:

slice

go

I want delete some elements from a slice, and https://github.com/golang/go/wiki/SliceTricks advise this slice-manipulation:

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

Then I coded below:

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

with go run hello.go, it panics:

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

How can I change this code to get right?

I tried below:

1st, with a goto statement:

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

it works, but too much iteration

2nd, use another slice sharing same backing array:

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

but not sure if this one is better or not.

3rd, from Remove elements in slice, with len operator:

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

which one should I take now?

with benchmark:

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

it seems delete all elements in one loop is better

like image 704
Simon Chen Avatar asked Dec 10 '22 16:12

Simon Chen


1 Answers

Iterate over the slice copying elements that you want to keep.

k := 0
for _, n := range slice {
    if n%3 != 0 { // filter
        slice[k] = n
        k++
    }
}
slice = slice[:k] // set slice len to remaining elements

The slice trick is useful in the case where a single element is deleted. If it's possible that more than one element will be deleted, then use the for loop above.

working playground example

like image 160
Bayta Darell Avatar answered Dec 29 '22 01:12

Bayta Darell