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.
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("\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))
}
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("\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)
}
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
答案 0 :(得分:4)
当您拥有int
类型的变量,并且想要编写一个递增其值的函数时,该怎么做?您可以将指针传递给变量,或者返回必须分配给原始变量的增量值。
例如(在Go Playground上尝试):
func inc(i int) int { i++; return i }
var i int = 2
inc(i)
fmt.Println(i) // This will be 2
在上面的代码中,您将i
传递给inc()
,从而使它递增并返回其值。原始i
当然不会改变,i
内的inc()
只是副本,与原始i
无关。要更改原件,您必须确定返回值:
i = inc(i)
或首先使用指针(在Go Playground上尝试使用指针):
func inc(i *int) { *i++ }
var i int = 2
inc(&i)
fmt.Println(i) // This will be 3
切片也一样。如果要/必须修改切片头(它是数据指针,长度和容量,请参见reflect.SliceHeader
),则必须将指针传递给该切片(不是很常见),或者必须返回您必须在调用方处分配的修改后的新切片头。这是更常用的解决方案,也是内置append()
遵循的方法。
对切片进行切片时(例如someslice[min:max]
),新切片将与原始切片共享后备阵列。这意味着,如果您修改新切片的 elements ,则原始切片也会观察到这些变化。因此,如果您从新切片中删除一个元素,然后将这些元素复制到已删除元素的位置,则原始切片的最后一个元素仍将存在,该位置将被原始切片“覆盖”。通常的做法是将最后一个元素清零,以便垃圾收集器可以回收其内存(如果它是指针类型)(或“类似”的切片,映射或通道)。有关详细信息,请参见Memory leak in golang slice和Does go garbage collect parts of slices?
直接回答您的问题:
- 如何避免出现“输出片段”? (以正确的方式打印,包含正确的元素,并具有预期的长度和容量)
如该答案所述:您将必须将指针传递给切片,并在FindAndRemoveFromFooSlice()
中修改指向的值,因此不必返回新切片。
- 为什么我尝试“就地移除”会导致“输入片段”的长度与过滤过程之前的长度相同?
您从未修改原始切片,而是将其传递给您,因此制作了一个副本,在FindAndRemoveFromFooSlice()
内,您只能修改副本(但您甚至都没有修改副本)。您返回了一个新切片,但未分配它,因此原始切片(标题)是完整的。
- 为什么“输入分片”的长度与我应用过滤过程之前的长度相同?如何进行删除操作以更改“输入切片”的长度?
这是由前两个问题回答的。
查看相关问题:
答案 1 :(得分:0)
我建议对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
}