Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

slice

go

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})]
like image 728
user2736464 Avatar asked Sep 02 '13 05:09

user2736464


People also ask

How do I remove an item from my slice?

To remove the first element, call remove(s, 0), to remove the second, call remove(s, 1), and so on and so forth. Hm, not really. This: s[i] = s[len(s)-1] definitely copies the last element to the element at index i . Then, return s[:len(s)-1] returns the slice without the last element.

How do you remove an element from an array slice?

Remove the last element of an array with slice slice() on an array named arr in this way: arr. slice(0, -1) . Here is a complete example using the same alphabet array from above, starting with an array of the first 6 alphabet letters. The slice method takes up to two parameters.

How do you remove the last element from a slice in Golang?

We will follow the below approach to remove the last item: Calculate the length of the slice. Re-slice the original slice to last before element.

How do you define a slice in Golang?

In Go language, a slice can be created and initialized using the following ways: Using slice literal: You can create a slice using the slice literal. The creation of slice literal is just like an array literal, but with one difference you are not allowed to specify the size of the slice in the square braces[].


2 Answers

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
}
like image 123
Stephen Weinberg Avatar answered Nov 05 '22 07:11

Stephen Weinberg


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}
like image 21
Meow Avatar answered Nov 05 '22 06:11

Meow