如何通过在切片上调用方法来删除其中的一个元素

huangapple go评论99阅读模式
英文:

How to remove an item from a slice by calling a method on the slice

问题

Go又让我困惑了。希望有人能帮忙。我创建了一个包含指向结构体(myStruct)的指针的切片(mySlice)。

问题出在"Remove"方法上。当我们在"Remove"方法内部时,一切都正常,但一旦返回后,切片的大小没有改变,所以我们会看到最后一个元素重复出现。

我最初尝试使用"Add"方法中使用的相同模式来编写"Remove"方法,但它无法编译通过,所以我将其注释掉了。

我可以通过将新创建的切片返回给调用函数来使其工作,但我不想这样做,因为mySlice(ms)是一个单例。

如果我还没有问够的话...

"Add"方法的代码是有效的,尽管我不确定为什么。据我所了解,"Add"方法接收一个指向切片头部的指针(这个3个项的"结构体")。根据我所读到的,切片的长度和容量在传递给方法时(通过值传递)不会被传递,所以也许通过传递一个切片的指针,方法可以看到和使用长度和容量,从而允许我们进行"追加"操作。如果这是真的,那为什么在"Remove"方法中同样的模式不起作用呢?

非常感谢大家的见解和帮助!

package main

import (
	"fmt"
)

type myStruct struct {
	a int
}
type mySlice []*myStruct

func (slc *mySlice) Add(str *myStruct) {
	*slc = append(*slc, str)
}

//无法编译通过,原因是:无法对slc进行切片(类型为*mySlice)
//func (slc *mySlice) Remove1(item int) {
//	*slc = append(*slc[:item], *slc[item+1:]...)
//}

func (slc mySlice) Remove(item int) {
	slc = append(slc[:item], slc[item+1:]...)
	fmt.Printf("Inside Remove = %s\n", slc)
}

func main() {
	ms := make(mySlice, 0)
	ms.Add(&myStruct{0})
	ms.Add(&myStruct{1})
	ms.Add(&myStruct{2})
	fmt.Printf("Before Remove:  Len=%d, Cap=%d, Data=%s\n", len(ms), cap(ms), ms)
	ms.Remove(1) //移除索引为1的元素(值也为1的元素)
	fmt.Printf("After Remove:  Len=%d, Cap=%d, Data=%s\n", len(ms), cap(ms), ms)
}

运行结果如下:

Before Remove:  Len=3, Cap=4, Data=[%!s(*main.myStruct=&{0}) %!s(*main.myStruct=&{1}) %!s(*main.myStruct=&{2})]

Inside Remove = [%!s(*main.myStruct=&{0}) %!s(*main.myStruct=&{2})]

After Remove:  Len=3, Cap=4, Data=[%!s(*main.myStruct=&{0}) %!s(*main.myStruct=&{2}) %!s(*main.myStruct=&{2})]
英文:

Go has stumped me again. Hopefully someone can help. I've created a slice (mySlice) that contains pointers to structs (myStruct).

The problem is the "Remove" method. When we're inside "Remove" everything is fine, but once we return, the slice size hasn't changed, and so we see the last element listed twice.

I originally tried writing "Remove" using the same pattern used in the "Add" method, but it wouldn't compile and has been commented out.

I can get it to work by returning the newly created slice to the calling function, but I don't want to do this because mySlice (ms) is a singleton.

And if I hadn't asked enough already...

The code for the "Add" method is working, although I'm not sure how. From what I can gather "Add" is receiving a pointer to the slice header (the 3 item "struct"). From what I've read, the length and capacity of an slice don't get passed to methods (when passing by value), so perhaps passing a pointer to the slice allows the method to see and use the length and capacity thereby allowing us to "append". If this is true, then why doesn't the same pattern work in "Remove"?

Thanks very much for everyone's insights and help!

package main

import (
	"fmt"
)

type myStruct struct {
	a int
}
type mySlice []*myStruct

func (slc *mySlice) Add(str *myStruct) {
	*slc = append(*slc, str)
}

//does not compile with reason: cannot slice slc (type *mySlice)
//func (slc *mySlice) Remove1(item int) {
//	*slc = append(*slc[:item], *slc[item+1:]...)
//}

func (slc mySlice) Remove(item int) {
	slc = append(slc[:item], slc[item+1:]...)
	fmt.Printf("Inside Remove = %s\n", slc)
}

func main() {
	ms := make(mySlice, 0)
	ms.Add(&myStruct{0})
	ms.Add(&myStruct{1})
	ms.Add(&myStruct{2})
	fmt.Printf("Before Remove:  Len=%d, Cap=%d, Data=%s\n", len(ms), cap(ms), ms)
	ms.Remove(1) //remove element 1 (which also has a value of 1)
	fmt.Printf("After Remove:  Len=%d, Cap=%d, Data=%s\n", len(ms), cap(ms), ms)
}

and the results...

Before Remove:  Len=3, Cap=4, Data=[%!s(*main.myStruct=&{0}) %!s(*main.myStruct=&{1}) %!s(*main.myStruct=&{2})]

Inside Remove = [%!s(*main.myStruct=&{0}) %!s(*main.myStruct=&{2})]

After Remove:  Len=3, Cap=4, Data=[%!s(*main.myStruct=&{0}) %!s(*main.myStruct=&{2}) %!s(*main.myStruct=&{2})]

答案1

得分: 17

你第一次使用 Remove1() 是正确的。Remove 函数会得到切片的副本,因此无法改变切片的长度。

你的 remove 函数的问题在于,根据 Go 语言的运算顺序,切片操作在解引用之前执行。

修复的方法是将 *slc = append(*slc[:item], *slc[item+1:]...) 改为 *slc = append((*slc)[:item], (*slc)[item+1:]...)

然而,为了提高可读性和可维护性,我建议使用以下代码:

func (slc *mySlice) Remove1(item int) {
    s := *slc
    s = append(s[:item], s[item+1:]...)
    *slc = s
}
英文:

You were right the first time with Remove1(). Remove gets a copy of the slice and therefore cannot change the length of the slice.

The issue in your remove function is that according to order of operations in Go, slicing comes before dereferencing.

The fix is to change *slc = append(*slc[:item], *slc[item+1:]...) to *slc = append((*slc)[:item], (*slc)[item+1:]...).

However I would recommend the following for readability and maintainability:

func (slc *mySlice) Remove1(item int) {
    s := *slc
    s = append(s[:item], s[item+1:]...)
    *slc = s
}

答案2

得分: 1

因为append不一定会返回对切片的相同引用地址,正如Stephen Weinberg所指出的那样。
解决这个限制的另一种方法是定义一个包装切片的结构体。

例如:

package main

import "fmt"

type IntList struct {
    intlist []int
}

func (il *IntList) Pop() {
    if len(il.intlist) == 0 { return }
    il.intlist = il.intlist[:len(il.intlist)-1]
}

func (il *IntList) Add(i... int) {
    il.intlist = append(il.intlist, i...)
}

func (il *IntList) String() string {
    return fmt.Sprintf("%#v",il.intlist)
}

func main() {
    intlist := &IntList{[]int{1,2,3}} 
    fmt.Println(intlist)
    intlist.Pop()
    fmt.Println(intlist)
    intlist.Add([]int{4,5,6}...)
    fmt.Println(intlist)
}

输出:

[]int{1, 2, 3}
[]int{1, 2}
[]int{1, 2, 4, 5, 6}
英文:

Because append would not necessarily return the same address of reference to the slice, as Stephen Weinberg has pointed out.
Another way to workaround with this limitation is defining a struct that wraps the slice.

for example:

package main

import "fmt"

type IntList struct {
	intlist []int
}

func (il *IntList) Pop() {
    if len(il.intlist) == 0 { return }
	il.intlist = il.intlist[:len(il.intlist)-1]
}

func (il *IntList) Add(i... int) {
	il.intlist = append(il.intlist, i...)
}

func (il *IntList) String() string {
	return fmt.Sprintf("%#v",il.intlist)
}

func main() {
	intlist := &IntList{[]int{1,2,3}} 
	fmt.Println(intlist)
	intlist.Pop()
	fmt.Println(intlist)
	intlist.Add([]int{4,5,6}...)
	fmt.Println(intlist)
}

output:

[]int{1, 2, 3}
[]int{1, 2}
[]int{1, 2, 4, 5, 6}

huangapple
  • 本文由 发表于 2013年9月2日 13:58:27
  • 转载请务必保留本文链接:https://go.coder-hub.com/18566499.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定