What's the Go idiom for Python's list.pop() method?
Asked Answered
K

7

19

In Python, I have the following:

i = series.index(s) # standard Python list.index() function
tmp = series.pop(i)
blah = f(tmp)
series.append(tmp)

In converting this to Go, I am looking for a similar way of retrieving an item from a slice by index, doing something with it, then putting the original item at the end of my slice.

From here, I have arrived at the following:

i = Index(series, s) // my custom index function...
tmp, series = series[i], series[i+1:]
blah := f(tmp)
series = append(series, tmp)

But this fails at the end of lists:

panic: runtime error: slice bounds out of range

How would I idiomatically translate this slice.pop() into Go?

Kukri answered 27/9, 2018 at 23:46 Comment(7)
What's in i and what's cap and len of series?Barbican
It would help if you shared your Index function. The wiki you listed also has a recipe to pop an element and get the rest of the slice.Orectic
Index returns an integer, i, corresponding to the index of series where the value s is located.Kukri
@Kukri if it returned that you wouldn't have had a panicBarbican
If you find yourself calling list.index or list.pop with an index argument in Python, it's a sign you may be using the wrong data structure. A set or dict (a map in Go) may be more appropriate, or possibly something else.Palladic
Also, you're not using blah.Palladic
Thanks, @user2357112, this is not a python question. Just worried about Go. I am using blah, just not in the example for simplicity.Kukri
J
22

The "Cut" trick in the linked document does what you want:

xs := []int{1, 2, 3, 4, 5}

i := 0 // Any valid index, however you happen to get it.
x := xs[i]
xs = append(xs[:i], xs[i+1:]...)
// Now "x" is the ith element and "xs" has the ith element removed.

Note that if you try to make a one-liner out of the get-and-cut operations you'll get unexpected results due to the tricky behavior of multiple assignments in which functions are called before other expressions are evaluated:

i := 0
x, xs := xs[i], append(xs[:i], xs[i+1:]...)
// XXX: x=2, xs=[]int{2, 3, 4, 5}

You can work around by wrapping the element access operation in any function call, such as the identity function:

i := 0
id := func(z int) { return z }
x, xs := id(xs[i]), append(xs[:i], xs[i+1:]...)
// OK: x=1, xs=[]int{2, 3, 4, 5}

However, at that point it's probably more clear to use separate assignments.

For completeness, a "cut" function and its usage could look like this:

func cut(i int, xs []int) (int, []int) {
  y := xs[i]
  ys := append(xs[:i], xs[i+1:]...)
  return y, ys
}

t, series := cut(i, series)
f(t)
series = append(series, t)
Johnnyjumpup answered 28/9, 2018 at 0:4 Comment(1)
Thanks, the language of "cut" I didn't get, but this worked. I would never have guessed the ...Kukri
A
9

If you want to write a function that does pop() in a similar way to python then you'll have to pass in a pointer to the object so the object can be modified, as pop both returns the value and alters the list

func pop(alist *[]int) int {
   f:=len(*alist)
   rv:=(*alist)[f-1]
   *alist=(*alist)[:f-1]
   return rv
}

func main() {
n:=[]int{1,2,3,4,5}
fmt.Println(n)
last:=pop(&n)
fmt.Println("last is",last)
fmt.Printf("list of n is now %v\n", n)
Azotic answered 28/9, 2018 at 7:40 Comment(2)
Is there any reason to use append here? Would *alist=(*alist)[:f-1] be wrong or less efficient?Absalom
I agree Nicolas, the append seems to be unnecessary. I retested it and it worked fine. I looked at the append implementation (briefly) and couldn't see any magic gotchas. Updated answerAzotic
L
3

You can declaretype intSlice []int and you can declare method pop() using that pointer receiver: func (l *intSlice) pop() int. Then you can call .pop() on instance of intSlice object. This becomes stylistically more similar to Python.

package main

import (
    "fmt"
)

type intSlice []int

func (l *intSlice) pop() int {
    length := len(*l)
    lastEle := (*l)[length-1]
    *l = (*l)[:length-1]
    return lastEle
}

func main() {

    mySlice := intSlice{1, 2, 3, 4, 5, 6}

    popped := mySlice.pop()

    fmt.Println(popped)
    fmt.Println(mySlice)

    popped = mySlice.pop()

    fmt.Println(popped)
    fmt.Println(mySlice)

}

Result:

6
[1 2 3 4 5]
5
[1 2 3 4]

Go Playground

Lions answered 30/9, 2018 at 5:30 Comment(0)
J
2

Another option would be to create a function that takes a pointer to an int slice which modifies the argument to remove the last value and return it:

func pop(xs *[]int) int {
  x := (*xs)[len(*xs)-1]   // Store the last value to return.
  *xs = (*xs)[:len(*xs)-1] // Remove the last value from the slice.
  return x
}

For example (Go Playground):

xs := []int{1, 2, 3} // => xs=[1, 2, 3]
x := pop(&xs)        // => xs=[1, 2],   x=3
Johnnyjumpup answered 29/11, 2021 at 17:5 Comment(0)
D
1

I'm not sure there's a direct equivalent of "pop()" ... but you can do something like this:

A Tour of Go

Slices can be created with the built-in make function; this is how you create dynamically-sized arrays.

The make function allocates a zeroed array and returns a slice that refers to that array:

a := make([]int, 5) // len(a)=5

To specify a capacity, pass a third argument to make:

b := make([]int, 0, 5) // len(b)=0, cap(b)=5

b = b[:cap(b)] // len(b)=5, cap(b)=5

b = b[1:] // len(b)=4, cap(b)=4

See also:

Duodenary answered 27/9, 2018 at 23:55 Comment(0)
S
1

I'd do something similar to what paulsm4 suggested:

package main

import (
    "fmt"
)

func main() {
    a := []int{1,2,3,4,5}
    i,b := pop(a)
    fmt.Println(i,b) // 5 [1 2 3 4]
}

func pop(a []int) (int,[]int) {
    return a[len(a)-1],a[:len(a)-1]
}

Go playground

Scythe answered 28/9, 2018 at 0:3 Comment(2)
that isn't quite how pop() works in python or perl or several other implementations. The input list is modified - see my answer belowAzotic
@Azotic yeah, using a pointer you wouldn't have to return the array, smart way to do it.Scythe
M
0

Here is a version that uses a pointer to modify the list in place, while supporting the optional "index" argument like Python pop. It uses generics to support any type of element in the slice, so you may need to modify for golang <1.18

func pop[T comparable](xs *[]T, index int) T {
if index < 0 {
    index = len(*xs) + index
}
x := (*xs)[index] // Store the last value to return.
if index < len(*xs)-1 {
    *xs = append((*xs)[:index], (*xs)[index+1:]...)
} else {
    *xs = (*xs)[:index]
}
return x
}
Margaux answered 6/5 at 0:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.