Can anyone suggest Go container for simple and fast FIF/queue, Go has 3 different containers: heap
, list
and vector
. Which one is more
Either vector or list should work, but vector is probably the way to go. I say this because vector will probably allocate less often than list and garbage collection (in the current Go implementation) is fairly expensive. In a small program it probably won't matter, though.
In fact, if what you want is a basic and easy to use fifo queue, slice provides all you need.
queue := make([]int, 0)
// Push to the queue
queue = append(queue, 1)
// Top (just get next element, don't remove it)
x = queue[0]
// Discard top element
queue = queue[1:]
// Is empty ?
if len(queue) == 0 {
fmt.Println("Queue is empty !")
}
Of course, we suppose that we can trust the inner implementation of append and slicing so that it avoid useless resize and reallocation. For basic usage, this is perfectly sufficient.
I also implement the queue from slice as above. However, It's not thread-safe. So I decided to add a lock (mutex lock) to guarantee thread-safe.
package queue
import (
"sync"
)
type Queue struct {
lock *sync.Mutex
Values []int
}
func Init() Queue {
return Queue{&sync.Mutex{}, make([]int, 0)}
}
func (q *Queue) Enqueue(x int) {
for {
q.lock.Lock()
q.Values = append(q.Values, x)
q.lock.Unlock()
return
}
}
func (q *Queue) Dequeue() *int {
for {
if (len(q.Values) > 0) {
q.lock.Lock()
x := q.Values[0]
q.Values = q.Values[1:]
q.lock.Unlock()
return &x
}
return nil
}
return nil
}
You can check my solution on github here simple queue
type Queue struct {
slice []int
len int
}
func newq() Queue {
q := Queue{}
q.slice = make([]int, 0)
q.len = 0
return q
}
func (q *Queue) Add(v int) {
q.slice = append(q.slice, v)
q.len++
}
func (q *Queue) PopLeft() int {
a := q.slice[0]
q.slice = q.slice[1:]
q.len--
return a
}
func (q *Queue) Pop() int {
a := q.slice[q.len-1]
q.slice = q.slice[:q.len-1]
q.len--
return a
}
For your basic need the code above would do
Double stack implementation:
O(1)
Enqueue
and Dequeue
and uses slices
(which tends to be better for cache misses).
type Queue struct{
enqueue, dequeue Stack
}
func (q *Queue) Enqueue(n *Thing){
q.enqueue.Push(n)
}
func (q *Queue) Dequeue()(*Thing, bool){
v, ok := q.dequeue.Pop()
if ok{
return v, true
}
for {
v, ok := d.enqueue.Pop()
if !ok{
break
}
d.dequeue.Push(v)
}
return d.dequeue.Pop()
}
type Stack struct{
v []*Thing
}
func (s *Stack)Push(n *Thing){
s.v=append(s.v, n)
}
func (s *Stack) Pop()(*Thing, bool){
if len(s.v) == 0 {
return nil, false
}
lastIdx := len(s.v)-1
v := s.v[lastIdx]
s.v=s.v[:lastIdx]
return v, true
}
I implemented a queue that will expand the underlying buffer automatically:
package types
// Note: this queue does not shrink the underlying buffer.
type queue struct {
buf [][4]int // change to the element data type that you need
head int
tail int
}
func (q *queue) extend(need int) {
if need-(len(q.buf)-q.head) > 0 {
if need-len(q.buf) <= 0 {
copy(q.buf, q.buf[q.head:q.tail])
q.tail = q.tail - q.head
q.head = 0
return
}
newSize := len(q.buf) * 2
if newSize == 0 {
newSize = 100
}
newBuf := make([][4]int, newSize)
copy(newBuf, q.buf[q.head:q.tail])
q.buf = newBuf
q.tail = q.tail - q.head
q.head = 0
}
}
func (q *queue) push(p [4]int) {
q.extend(q.tail + 1)
q.buf[q.tail] = p
q.tail++
}
func (q *queue) pop() [4]int {
r := q.buf[q.head]
q.head++
return r
}
func (q *queue) size() int {
return q.tail - q.head
}
// put the following into queue_test.go
package types
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestQueue(t *testing.T) {
const total = 1000
q := &queue{}
for i := 0; i < total; i++ {
q.push([4]int{i, i, i, i})
assert.Equal(t, i+1, q.size())
}
for i := 0; i < total; i++ {
v := q.pop()
assert.Equal(t, [4]int{i, i, i, i}, v)
assert.Equal(t, total-1-i, q.size())
}
}