I have the following test that is printing the original input slice (after the filtering) without the element that has been removed, but with an extra element at the end making the input slice of the same length, even if after the filtering it should be shorter.

I've gone through this doc https://github.com/golang/go/wiki/SliceTricks#delete However I think I am missing some gotchas about Go, because it seems I am using slices with the wrong approach.

  • how can I avoid to have an "output slice"? (which is printed in the correct way, containing the right elements, having the expected length and capacity)
  • why my attempt at "removing inplace" leads to having the "input slice" with the same length as it had before the filtering process?
  • why the "input slice" has the same length as before I was applying the filtering process? How can I make the remove operation to change the length of the "input slice"?

This is the code:

package foo

import (
    "fmt"
    "log"
    "math/rand"
    "testing"
)

type FooItem struct {
    Id       int
    Category string
    Value    float64
}

const minRand = 0
const maxRand = 10

const maxSliceLen = 3

var inFooSlice []FooItem

func init() {
    for i := 1; i <= maxSliceLen; i++ {
        inFooSlice = append(inFooSlice, FooItem{
            Id:       i,
            Category: "FooCat",
            Value:    minRand + rand.Float64()*(maxRand-minRand),
        })
    }
}

// this is the function I am testing
func FindAndRemoveFromFooSlice(iFilter int, inSl []FooItem) (*FooItem, []FooItem) {

    inLen := len(inSl)
    outSl := make([]FooItem, inLen)

    for idx, elem := range inSl {
        if elem.Id == iFilter {
            log.Printf("Loop ID %v", idx)

            // check these docs: https://github.com/golang/go/wiki/SliceTricks#delete
            outSl = inSl[:idx+copy(inSl[idx:], inSl[idx+1:inLen])]
            outSl = outSl[:inLen-1]

            return &elem, outSl
        }
    }
    return nil, nil
}

func TestFoo(t *testing.T) {
    fmt.Printf("
Original (PRE) slice
")
    fmt.Println(inFooSlice)
    fmt.Println(len(inFooSlice))
    fmt.Println(cap(inFooSlice))

    idFilter := 1

    fePtr, outFooSlice := FindAndRemoveFromFooSlice(idFilter, inFooSlice)

    fmt.Printf("
Original (POST) slice
")
    fmt.Println(inFooSlice)
    fmt.Println(len(inFooSlice))
    fmt.Println(cap(inFooSlice))

    fmt.Printf("
Filtered element
")
    fmt.Println(*fePtr)

    fmt.Printf("
Output slice
")
    fmt.Println(outFooSlice)
    fmt.Println(len(outFooSlice))
    fmt.Println(cap(outFooSlice))
}

This is the output of the test execution:

$ go test -v -run TestFoo
=== RUN   TestFoo

Original (PRE) slice
[{1 FooCat 6.046602879796196} {2 FooCat 9.405090880450125} {3 FooCat 6.645600532184904}]
3
4
2019/05/31 12:53:30 Loop ID 0

Original (POST) slice
[{2 FooCat 9.405090880450125} {3 FooCat 6.645600532184904} {3 FooCat 6.645600532184904}]
3
4

Filtered element
{1 FooCat 6.046602879796196}

Output slice
[{2 FooCat 9.405090880450125} {3 FooCat 6.645600532184904}]
2
4
--- PASS: TestFoo (0.00s)
PASS
ok      git.openenergi.net/scm/flex/service/common  0.008s

Update on the "input slice as pointer"

OK, so assuming I would like to deal with the original input slice, i.e. no copy or output slice.

pointedInSl[inLen-1] = FooItem{}

This is the code:

func FindAndRemoveFromFooSliceInPlace(iFilter int, inSl *[]FooItem) *FooItem {
    pointedInSl := *inSl
    inLen := len(pointedInSl)
    for idx, elem := range pointedInSl {
        if elem.Id == iFilter {
            log.Printf("Loop ID %v", idx)

            // check these docs: https://github.com/golang/go/wiki/SliceTricks#delete
            pointedInSl = append(pointedInSl[:idx], pointedInSl[idx+1:inLen]...)
            // pointedInSl[inLen-1] = FooItem{} // why this throws a runtime "panic: runtime error: index out of range" ???
            pointedInSl = pointedInSl[:inLen-1]

            return &elem
        }
    }
    return nil
}

func TestFooInPlace(t *testing.T) {
    fmt.Printf("
Original (PRE) slice
")
    fmt.Println(inFooSlice)
    fmt.Println(len(inFooSlice))
    fmt.Println(cap(inFooSlice))

    idFilter := 1

    fePtr := FindAndRemoveFromFooSliceInPlace(idFilter, &inFooSlice)

    fmt.Printf("
Original (POST) slice
")
    fmt.Println(inFooSlice)
    fmt.Println(len(inFooSlice))
    fmt.Println(cap(inFooSlice))

    fmt.Printf("
Filtered element
")
    fmt.Println(*fePtr)
}

This is the weird output:

$ go test -v -run TestFooInPlace
=== RUN   TestFooInPlace

Original (PRE) slice
[{1 FooCat 6.046602879796196} {2 FooCat 9.405090880450125} {3 FooCat 6.645600532184904}]
3
4
2019/05/31 16:32:38 Loop ID 0

Original (POST) slice
[{2 FooCat 9.405090880450125} {3 FooCat 6.645600532184904} {3 FooCat 6.645600532184904}]
3
4

Filtered element
{1 FooCat 6.046602879796196}
--- PASS: TestFooInPlace (0.00s)
PASS
ok      git.openenergi.net/scm/flex/service/common  0.007s