gpt4 book ai didi

loops - 从 Golang 中的 slice 中删除

转载 作者:IT王子 更新时间:2023-10-29 01:53:32 24 4
gpt4 key购买 nike

我有以下测试打印原始输入 slice (过滤后),没有已删除的元素,但在末尾有一个额外的元素使输入 slice 具有相同的长度,即使在过滤之后它应该更短。

我已经阅读了这个文档 https://github.com/golang/go/wiki/SliceTricks#delete然而,我认为我遗漏了一些关于 Go 的陷阱,因为我似乎以错误的方式使用 slice 。

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

这是代码:

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

关于“输入 slice 作为指针”的更新

好的,假设我想处理原始输入 slice ,即没有副本或输出 slice 。

  • 为什么以下代码会在注释的代码行中引发运行时 panic ? (pointedInSl[inLen-1] = FooItem{})
  • 为什么打印的 slice (应用函数后)在其末尾包含 2 个相同的项目?如何删除最后一个冗余元素?
  • 为什么应用函数后的 slice 长度与应用函数前的 slice 长度相同?
  • 如何使原始 slice 缩小 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(),它会递增它并返回它的值。原来的i当然不会变,inc()里面的i只是一个副本,独立于原来的i。要更改原始值,您必须评估返回值:

i = inc(i)

或者首先使用指针(在 Go Playground 上尝试):

func inc(i *int) { *i++ }

var i int = 2
inc(&i)
fmt.Println(i) // This will be 3

slice 也是如此。如果你想/必须修改 slice 头(这是一个数据指针、长度和容量,参见 reflect.SliceHeader ),你要么必须传递一个指向该 slice 的指针(不是很常见),要么必须返回修改后的,您必须在调用者处分配的新 slice header 。这是更常用的解决方案,这是内置的方法 append()也跟着。

当您 slice 时,(例如 someslice[min:max]),新 slice 将与原始 slice 共享支持数组。这意味着如果您修改新 slice 的元素,原始 slice 也会观察到这些变化。因此,如果您从新 slice 中删除一个元素并将元素复制到被删除元素的位置,则原始 slice 的最后一个元素仍将在那里,它被原始 slice “覆盖”。通常的做法是将最后一个元素置零,以便垃圾收集器可以回收它的内存,如果它是指针类型(或“类似”如 slice 、映射或 channel )。有关详细信息,请参阅 Memory leak in golang sliceDoes go garbage collect parts of slices?

直接回答您的问题:

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

如本回答所述:您必须将指针传递给您的 slice ,并在 FindAndRemoveFromFooSlice() 中修改指向的值,因此您不必返回新 slice 。

  • why my attempt at "removing inplace" leads to having the "input slice" with the same length as it had before the filtering process?

您从未修改过原始 slice ,您传递了它以便创建一个副本,而在 FindAndRemoveFromFooSlice() 中您只能修改副本(但您甚至没有修改副本)。你返回一个新的 slice ,但你没有分配它,所以原始 slice (头)是完整的。

  • 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"?

前两个问题已经回答了这一点。

查看相关问题:

Are golang slices pass by value?

slice vs map to be used in parameter

关于loops - 从 Golang 中的 slice 中删除,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56394632/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com