loops - 在 Golang 中从切片中删除
问题描述
我有以下测试正在打印原始输入切片(过滤后),没有已删除的元素,但最后有一个额外的元素使输入切片的长度相同,即使在过滤后它应该是更短。
我已经浏览了这个文档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
更新“输入切片作为指针”
好的,假设我想处理原始输入切片,即没有复制或输出切片。
- 为什么以下代码在注释的代码行中引发运行时恐慌?(
pointedInSl[inLen-1] = FooItem{}
) - 为什么打印的切片(应用函数后)在其末尾包含 2 个相同的项目?如何删除最后一个冗余元素?
- 为什么应用函数后的切片长度与应用函数前的切片长度相同?
- 如何使原始切片缩小 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
解决方案
当你有一个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()
which 递增它并返回它的值。原件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]
),新切片将与原始切片共享支持数组。这意味着如果您修改新切片的元素,原始切片也会观察到这些变化。因此,如果您从新切片中删除一个元素并将元素复制到已删除元素的位置,则原始切片的最后一个元素仍然存在,它被原始切片“覆盖”。通常的做法是将最后一个元素归零,以便垃圾收集器可以在它是指针类型(或“类似”,如切片、映射或通道)时回收其内存。有关详细信息,请参阅golang slice 中的内存泄漏和Go 垃圾是否会收集部分切片?
直接回答你的问题:
- 我怎样才能避免有一个“输出切片”?(以正确的方式打印,包含正确的元素,具有预期的长度和容量)
如本答案所述:您必须将指针传递给切片,并修改 中的指向值FindAndRemoveFromFooSlice()
,因此您不必返回新切片。
- 为什么我尝试“移除就地”会导致“输入切片”的长度与过滤过程之前的长度相同?
您从未修改过原始切片,而是将其传递给了副本,并且在内部FindAndRemoveFromFooSlice()
您只能修改副本(但您甚至没有修改副本)。您返回一个新切片,但您没有分配它,因此原始切片(标题)是完整的。
- 为什么“输入切片”的长度与我应用过滤过程之前的长度相同?如何进行删除操作以更改“输入切片”的长度?
前两个问题回答了这个问题。
查看相关问题:
推荐阅读
- java - Android Studio Volley 本地连接失败
- javascript - 如何在 js 代码(nodejs 服务器)中获取 SCSS 变量?
- javascript - MongoDB 发出 POST 请求时返回空错误对象
- file-upload - 无法读取未定义的属性“getChildren”
- spring - 如何在 Spring 中从 AWS ECS 任务定义中读取环境变量?
- java - 双向链表在列表末尾返回 null
- arrays - Powershell 比较和合并两个数组
- python - Olx的api访问问题
- javascript - 数据表在调整大小时不尊重屏幕宽度
- kotlin - 服务器端 jvm 上的 Ktor,协程 native-mt 或 vanilla