How do I (succinctly) remove the first element from a slice in Go?

50,480

Solution 1

Did you try these?

Pop from queue

x, a = a[0], a[1:]

Pop from stack

x, a = a[len(a)-1], a[:len(a)-1]

Push

a = append(a, x)

From: https://code.google.com/p/go-wiki/wiki/SliceTricks

Solution 2

If you want a ring buffer or FIFO structure then using a slice as in @Everton's answer will cause garbage collection problems as the underlying array may grow indefinitely.

The easiest way to do this in go, provided you don't mind having a limited size, is to use a channel which is also safe for concurrent access. This is such a common idiom in go that you wouldn't usually bother wrapping it in a type like the below.

Eg (Playground)

package main

import "fmt"

type Queue struct {
    elements chan interface{}
}

func NewQueue(size int) *Queue {
    return &Queue{
        elements: make(chan interface{}, size),
    }
}

func (queue *Queue) Push(element interface{}) {
    select {
    case queue.elements <- element:
    default:
        panic("Queue full")
    }
}

func (queue *Queue) Pop() interface{} {
    select {
    case e := <-queue.elements:
        return e
    default:
        panic("Queue empty")
    }
    return nil
}

func main() {
    q := NewQueue(128)

    q.Push(1)
    q.Push(2)
    q.Push(3)
    fmt.Printf("Pop %d\n", q.Pop())
    fmt.Printf("Pop %d\n", q.Pop())
    fmt.Printf("Pop %d\n", q.Pop())
    fmt.Printf("Pop %d\n", q.Pop())

}
Share:
50,480
modocache
Author by

modocache

LLVM, Swift, and swift-corelibs-xctest committer. Creator of Quick, the Swift (and Objective-C) testing framework. Former Japanese literature translator.

Updated on June 06, 2020

Comments

  • modocache
    modocache almost 4 years

    I've built a simple queue in Go. It uses an internal slice to keep track of its elements. Elements are pushed onto the queue by appending to the slice. I'd like to implement .Pop() by removing the first element in elements.

    In many other languages, "popping" the first element of a list is a one-liner, which leads me to believe my implementation below is sloppy and verbose. Is there a better way?

    type Queue struct {
        elements []interface{}
    }
    
    func (queue *Queue) Push(element interface{}) {
        queue.elements = append(queue.elements, element)
    }
    
    func (queue *Queue) Pop() interface{} {
        element := queue.elements[0]
        if len(queue.elements) > 1 {
            queue.elements = queue.elements[1:]
        } else {
            queue.elements = make([]interface{}, 0)
        }
        return element
    }
    

    Please note that I wish for the Queue to panic if len(queue.elements) == 0. It's not an oversight that I don't check the bounds.

  • modocache
    modocache almost 10 years
    Boy, is my face red. I read that article, and thought I had tried the "pop from queue" example, but I tried again and it worked as I had hoped. Thanks! (And sorry for the silly question.)
  • Evan
    Evan almost 10 years
    If efficiency is important, you may want to use a ring-buffer style to avoid excessive re-allocation and garbage collection. Something like github.com/eapache/channels/blob/master/queue.go
  • siritinga
    siritinga almost 10 years
    I'm just wondering if after popping from the queue, the first element will be ever garbaged collected. If I keep popping the first element and appending at the end, will I end with a huge array? the unused elements are also copied if append() copies the array to a new one?
  • modocache
    modocache almost 10 years
    I'm still new to Go, so I might take me a while to wrap my head around this, but thanks for the answer! I'll come back to it after I learn more about channels.
  • Rick-777
    Rick-777 almost 10 years
    Channels happen to provide a useful and efficient tool for making ring buffers like this. As originally conceived by C.A.R.Hoare though, channels are intended to be used between processes (goroutines); this pattern is rather different. Be aware that if the channel buffer becomes full, and the only consuming thread (calling Pop) is also trying to call Push, a deadlock can occur. @Everton's approach doesn't have this drawback; the garbage collection problem could be solved a different way.
  • Evan
    Evan almost 10 years
    The first element will only be garbage collected when enough new elements have been added that the slice is reallocated and the removed elements can be discarded. In my ring-buffer sample code, the elements are explicitly niled when removed in order to permit immediate garbage collection. (The unused elements are not copied if append resizes the array).
  • Nick Craig-Wood
    Nick Craig-Wood almost 10 years
    @Rick-777 the deadlock is easily solved with another select in the Push method.
  • Rick-777
    Rick-777 almost 10 years
    Can you edit the code example to show how it's done?
  • Nick Craig-Wood
    Nick Craig-Wood almost 10 years
    @Rick-777 Have updated the answer to show how to detect queue full.
  • modocache
    modocache almost 10 years
    Nick: Thanks so much! What an ingenious use of channels! The deadlock problem and its detection is also very illuminating. @Rick-777, you say the garbage collection problem could be solved differently. Could you explain how?
  • Nick Craig-Wood
    Nick Craig-Wood almost 10 years
    I think the main problem with this approach is that there is potentially an allocation on every queue insert - playground example
  • Nick Craig-Wood
    Nick Craig-Wood almost 10 years
    @modocache I'd say if you are communicating between go routines, just use channels - that is what they are for. You don't need queue empty or full detection - the runtime will pause and unpause the goroutines for you automatically. Otherwise you could use this solution which is type safe but bounded, or you could use the code mentioned by Evan which doesn't have any allocation or garbage collection problems.
  • Everton
    Everton almost 10 years
    @NickCraig-Wood Great illustration.
  • Evan
    Evan almost 10 years
    I extracted my ring-buffer based queue into its own package at github.com/eapache/queue so that other people can import it if they want to use it.