小编典典

从Golang中的切片中删除

go

我进行了以下测试,即打印原始输入切片(过滤后),而没有删除元素,但是最后有一个额外的元素,使输入切片具有相同的长度,即使过滤后应该较短。

我已经看过这篇文档https://github.com/golang/go/wiki/SliceTricks#delete
但是我认为我缺少有关Go的一些技巧,因为似乎我使用的切片方法错误。

  • 如何避免出现“输出片段”?(以正确的方式打印,包含正确的元素,并具有预期的长度和容量)
  • 为什么我尝试“移除就位”会导致“输入片段”的长度与过滤过程之前的长度相同?
  • 为什么“输入分片”的长度与我应用过滤过程之前的长度相同?如何进行删除操作以更改“输入切片”的长度?

这是代码:

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("\nOriginal (PRE) slice\n")
    fmt.Println(inFooSlice)
    fmt.Println(len(inFooSlice))
    fmt.Println(cap(inFooSlice))

    idFilter := 1

    fePtr, outFooSlice := FindAndRemoveFromFooSlice(idFilter, inFooSlice)

    fmt.Printf("\nOriginal (POST) slice\n")
    fmt.Println(inFooSlice)
    fmt.Println(len(inFooSlice))
    fmt.Println(cap(inFooSlice))

    fmt.Printf("\nFiltered element\n")
    fmt.Println(*fePtr)

    fmt.Printf("\nOutput slice\n")
    fmt.Println(outFooSlice)
    fmt.Println(len(outFooSlice))
    fmt.Println(cap(outFooSlice))
}

这是测试执行的输出:

$ 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

更新“输入切片作为指针”

OK,假设我要处理原始输入切片,即没有副本或输出切片。

  • 为什么下面的代码在注释的代码行中引发运行时恐慌?(pointedInSl[inLen-1] = FooItem{}
  • 为什么打印的切片(应用功能后)在其末尾包含两个相同的主题?我如何删除最后一个冗余元素?
  • 为什么应用该功能后的条带长度仍然与应用该功能之前的条带长度相同?
  • 如何使原始切片缩小1(即输出长度=原始长度-1)?

这是代码:

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("\nOriginal (PRE) slice\n")
    fmt.Println(inFooSlice)
    fmt.Println(len(inFooSlice))
    fmt.Println(cap(inFooSlice))

    idFilter := 1

    fePtr := FindAndRemoveFromFooSliceInPlace(idFilter, &inFooSlice)

    fmt.Printf("\nOriginal (POST) slice\n")
    fmt.Println(inFooSlice)
    fmt.Println(len(inFooSlice))
    fmt.Println(cap(inFooSlice))

    fmt.Printf("\nFiltered element\n")
    fmt.Println(*fePtr)
}

这是奇怪的输出:

$ 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

阅读 307

收藏
2020-07-02

共1个答案

小编典典

我建议对icza答案进行编辑,以在其底部提供一个最小的工作代码示例,以说明他提供的有用信息。被拒绝,指出它作为编辑是没有意义的,它应该被写为评论或答案,所以就在这里(主要归功于icza):

最低工作代码示例(带注释的内容):

// use a pointer for the input slice so then it is changed in-place
func FindAndRemoveFromFooSliceInPlace(iFilter int, inSl *[]FooItem) *FooItem {
    pointedInSl := *inSl // dereference the pointer so then we can use `append`
    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 = pointedInSl[:inLen-1]
            *inSl = pointedInSl // assigning the new slice to the pointed value before returning

            return &elem
        }
    }
    return nil
}
2020-07-02