initial commit

This commit is contained in:
Andrew Gillis 2018-04-23 23:06:09 -04:00
commit 4e0a647678
6 changed files with 810 additions and 0 deletions

26
.gitignore vendored Normal file
View file

@ -0,0 +1,26 @@
*~
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
*.test
*.prof

16
.travis.yml Normal file
View file

@ -0,0 +1,16 @@
language: go
go:
- "1.8"
- "1.9"
- "1.10"
- "tip"
before_script:
- go vet ./...
script:
- ./go.test.sh
after_success:
- bash <(curl -s https://codecov.io/bash)

56
README.md Normal file
View file

@ -0,0 +1,56 @@
# deque
[![Build Status](https://travis-ci.org/gammazero/deque.svg)](https://travis-ci.org/gammazero/deque)
[![Go Report Card](https://goreportcard.com/badge/github.com/gammazero/deque)](https://goreportcard.com/report/github.com/gammazero/deque)
[![codecov](https://codecov.io/gh/gammazero/deque/branch/master/graph/badge.svg)](https://codecov.io/gh/gammazero/deque)
[![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/gammazero/deque/blob/master/LICENSE)
A fast ring-buffer deque (double-ended queue) implementation.
[![GoDoc](https://godoc.org/github.com/gammazero/deque?status.png)](https://godoc.org/github.com/gammazero/deque)
This deque implementation automatically re-sizes by powers of two, growing when additional capacity is needed and shrinking when only a quarter of the capacity is used. This allows bitwise arithmetic for all calculations.
The ring-buffer implementation significantly improves memory and time performance with fewer GC pauses, compared to implementations based on slices and linked lists.
For maximum speed, this deque implementation leaves concurrency safety up to the application to provide, however is best for the application if needed at all.
## Installation
```
$ go get github.com/gammazero/deque
```
## Example
```go
package main
import (
"fmt"
"github.com/gammazero/deque"
)
func main() {
var q deque.Deque
q.PushBack("foo")
q.PushBack("bar")
q.PushBack("baz")
fmt.Println(q.Len()) // Prints: 3
fmt.Println(q.Front()) // Prints: foo
fmt.Println(q.Back()) // Prints: baz
q.PopFront() // remove "foo"
q.PopBack() // remove "baz"
q.PushFront("hello")
q.PushBack("world")
// Print: hello bar world
for i := 0; i < q.Len(); i++ {
fmt.Print(q.PeekAt(i), " ")
}
fmt.Println()
}
```

193
deque.go Normal file
View file

@ -0,0 +1,193 @@
/*
Package deque provides a fast, ring-buffer deque (double-ended queue) that
automatically re-sizes by powers of two. This allows bitwise arithmetic for
all calculations. The ring-buffer implementation significantly improves memory
and time performance with fewer GC pauses, compared to implementations based on
slices and linked lists.
For maximum speed, this deque implementation leaves concurrency safety up to
the application to provide, however is best for the application if needed at
all.
Queue (FIFO) operations are supported using PushBack() and PopFront(). Stack
(LIFO) operations are supported using PushBack() and PopBack().
*/
package deque
// minCapacity is the smallest capacity that deque may have.
// Must be power of 2 for bitwise modulus: x % n == x & (n - 1).
const minCapacity = 16
// Deque represents a single instance of the deque data structure.
type Deque struct {
buf []interface{}
head int
tail int
count int
}
// Len returns the number of elements currently stored in the queue.
func (q *Deque) Len() int {
return q.count
}
// PushBack appends an element to the back of the queue. Implements FIFO when
// elements are removed with PopFront(), and LIFO when elements are removed
// with PopBack().
func (q *Deque) PushBack(elem interface{}) {
if q.count == len(q.buf) {
q.resize()
}
q.buf[q.tail] = elem
// calculate new tail using bitwise modulus
q.tail = (q.tail + 1) & (len(q.buf) - 1)
q.count++
}
// PushFront prepends an element to the front of the queue.
func (q *Deque) PushFront(elem interface{}) {
if q.count == len(q.buf) {
q.resize()
}
// calculate new head using bitwise modulus
q.head = (q.head - 1) & (len(q.buf) - 1)
q.buf[q.head] = elem
q.count++
}
// PopFront removes and returns the element from the front of the queue.
// Implements FIFO when used with PushBack(). If the queue is empty, the call
// panics.
func (q *Deque) PopFront() interface{} {
if q.count <= 0 {
panic("deque: PopFront() called on empty queue")
}
ret := q.buf[q.head]
q.buf[q.head] = nil
// Calculate new head using bitwise modulus.
q.head = (q.head + 1) & (len(q.buf) - 1)
q.count--
// Resize down if buffer 1/4 full.
if len(q.buf) > minCapacity && (q.count<<2) == len(q.buf) {
q.resize()
}
return ret
}
// PopBack removes and returns the element from the back of the queue.
// Implements LIFO when used with PushBack(). If the queue is empty, the call
// panics.
func (q *Deque) PopBack() interface{} {
if q.count <= 0 {
panic("deque: PopBack() called on empty queue")
}
// Calculate new tail using bitwise modulus.
q.tail = (q.tail - 1) & (len(q.buf) - 1)
// Remove value at tail.
ret := q.buf[q.tail]
q.buf[q.tail] = nil
q.count--
// Resize down if buffer 1/4 full.
if len(q.buf) > minCapacity && (q.count<<2) == len(q.buf) {
q.resize()
}
return ret
}
// Front returns the element at the front of the queue. This is the element
// that would be returned by PopFront(). This call panics if the queue is
// empty.
func (q *Deque) Front() interface{} {
if q.count <= 0 {
panic("deque: Front() called on empty queue")
}
return q.buf[q.head]
}
// Back returns the element at the back of the queue. This is the element
// that would be returned by PopBack(). This call panics if the queue is
// empty.
func (q *Deque) Back() interface{} {
if q.count <= 0 {
panic("deque: Back() called on empty queue")
}
// bitwise modulus
return q.buf[(q.tail-1)&(len(q.buf)-1)]
}
// PeekAt returns the element at index i in the queue. This method accepts
// both positive and negative index values. PeekAt(0) refers to the first
// (earliest added) element and is the same as Front(). PeekAt(-1) refers to
// the last (latest added) element and is the same as Back(). If the index
// is invalid, the call panics.
func (q *Deque) PeekAt(i int) interface{} {
// If indexing backwards, convert to positive index.
if i < 0 {
i += q.count
}
if i < 0 || i >= q.count {
panic("deque: PeekAt() called with index out of range")
}
// bitwise modulus
return q.buf[(q.head+i)&(len(q.buf)-1)]
}
// Clear removes all elements from the queue, but retains the current capacity.
func (q *Deque) Clear() {
// bitwise modulus
mbits := len(q.buf) - 1
for h := q.head; h != q.tail; h = (h + 1) & mbits {
q.buf[h] = nil
}
q.head = 0
q.tail = 0
q.count = 0
}
// Copy copies elements from the queue to the destination slice, from front to
// back. Copy returns the number of elements copied, which will be the minimum
// of q.Len() and len(dst).
func (q *Deque) Copy(dst []interface{}) int {
count := q.count
if len(dst) < q.count {
count = len(dst)
}
if count == 0 {
return 0
}
if q.head+count <= len(q.buf) {
copy(dst, q.buf[q.head:q.head+count])
} else {
n := copy(dst, q.buf[q.head:])
copy(dst[n:], q.buf[:count-n])
}
return count
}
// resize resizes the deque to fit exactly twice its current contents.
// This results in shrinking if the queue is less than half-full, or growing
// the queue when it is full.
func (q *Deque) resize() {
if len(q.buf) == 0 {
q.buf = make([]interface{}, minCapacity)
return
}
newBuf := make([]interface{}, q.count<<1)
if q.tail > q.head {
copy(newBuf, q.buf[q.head:q.tail])
} else {
n := copy(newBuf, q.buf[q.head:])
copy(newBuf[n:], q.buf[:q.tail])
}
q.head = 0
q.tail = q.count
q.buf = newBuf
}

507
deque_test.go Normal file
View file

@ -0,0 +1,507 @@
package deque
import "testing"
func TestEmpty(t *testing.T) {
var q Deque
if q.Len() != 0 {
t.Error("q.Len() =", q.Len(), "expect 0")
}
}
func TestFrontBack(t *testing.T) {
var q Deque
q.PushBack("foo")
q.PushBack("bar")
q.PushBack("baz")
if q.Front() != "foo" {
t.Error("wrong value at front of queue")
}
if q.Back() != "baz" {
t.Error("wrong value at back of queue")
}
if q.PopFront() != "foo" {
t.Error("wrong value removed from front of queue")
}
if q.Front() != "bar" {
t.Error("wrong value remaining at front of queue")
}
if q.Back() != "baz" {
t.Error("wrong value remaining at back of queue")
}
if q.PopBack() != "baz" {
t.Error("wrong value removed from back of queue")
}
if q.Front() != "bar" {
t.Error("wrong value remaining at front of queue")
}
if q.Back() != "bar" {
t.Error("wrong value remaining at back of queue")
}
}
func TestGrowShrink(t *testing.T) {
var q Deque
for i := 0; i < minCapacity*2; i++ {
if q.Len() != i {
t.Error("q.Len() =", q.Len(), "expected", i)
}
q.PushBack(i)
}
// Check that all values are as expected.
for i := 0; i < minCapacity*2; i++ {
if q.PeekAt(i) != i {
t.Errorf("q.PeekAt(%d) = %d, expected %d", i, q.PeekAt(i), i)
}
}
bufLen := len(q.buf)
// Remove from back.
for i := minCapacity * 2; i > 0; i-- {
if q.Len() != i {
t.Error("q.Len() =", q.Len(), "expected", i)
}
x := q.PopBack()
if x != i-1 {
t.Error("q.PopBack() =", x, "expected", i-1)
}
}
if q.Len() != 0 {
t.Error("q.Len() =", q.Len(), "expected 0")
}
if len(q.buf) == bufLen {
t.Error("queue buffer did not shrink")
}
// Fill up queue again.
for i := 0; i < minCapacity*2; i++ {
if q.Len() != i {
t.Error("q.Len() =", q.Len(), "expected", i)
}
q.PushBack(i)
}
bufLen = len(q.buf)
// Remove from Front
for i := 0; i < minCapacity*2; i++ {
if q.Len() != minCapacity*2-i {
t.Error("q.Len() =", q.Len(), "expected", minCapacity*2-i)
}
x := q.PopFront()
if x != i {
t.Error("q.PopBack() =", x, "expected", i)
}
}
if q.Len() != 0 {
t.Error("q.Len() =", q.Len(), "expected 0")
}
if len(q.buf) == bufLen {
t.Error("queue buffer did not shrink")
}
}
func TestDequeSimple(t *testing.T) {
q := new(Deque)
for i := 0; i < minCapacity; i++ {
q.PushBack(i)
}
for i := 0; i < minCapacity; i++ {
if q.Front() != i {
t.Error("peek", i, "had value", q.Front())
}
x := q.PopFront()
if x != i {
t.Error("remove", i, "had value", x)
}
}
q.Clear()
for i := 0; i < minCapacity; i++ {
q.PushFront(i)
}
for i := minCapacity - 1; i >= 0; i-- {
x := q.PopFront()
if x != i {
t.Error("remove", i, "had value", x)
}
}
}
func TestDequeWrap(t *testing.T) {
q := new(Deque)
for i := 0; i < minCapacity; i++ {
q.PushBack(i)
}
for i := 0; i < 3; i++ {
q.PopFront()
q.PushBack(minCapacity + i)
}
for i := 0; i < minCapacity; i++ {
if q.Front().(int) != i+3 {
t.Error("peek", i, "had value", q.Front())
}
q.PopFront()
}
}
func TestDequeWrapReverse(t *testing.T) {
q := new(Deque)
for i := 0; i < minCapacity; i++ {
q.PushFront(i)
}
for i := 0; i < 3; i++ {
q.PopBack()
q.PushFront(minCapacity + i)
}
for i := 0; i < minCapacity; i++ {
if q.Back().(int) != i+3 {
t.Error("peek", i, "had value", q.Front())
}
q.PopBack()
}
}
func TestDequeLen(t *testing.T) {
q := new(Deque)
if q.Len() != 0 {
t.Error("empty queue length not 0")
}
for i := 0; i < 1000; i++ {
q.PushBack(i)
if q.Len() != i+1 {
t.Error("adding: queue with", i, "elements has length", q.Len())
}
}
for i := 0; i < 1000; i++ {
q.PopFront()
if q.Len() != 1000-i-1 {
t.Error("removing: queue with", 1000-i-i, "elements has length", q.Len())
}
}
}
func TestDequePeekAt(t *testing.T) {
q := new(Deque)
for i := 0; i < 1000; i++ {
q.PushBack(i)
for j := 0; j < q.Len(); j++ {
if q.PeekAt(j).(int) != j {
t.Errorf("index %d doesn't contain %d", j, j)
}
}
}
}
func TestDequePeekAtNegative(t *testing.T) {
q := new(Deque)
for i := 0; i < 1000; i++ {
q.PushBack(i)
for j := 1; j <= q.Len(); j++ {
if q.PeekAt(-j).(int) != q.Len()-j {
t.Errorf("index %d doesn't contain %d", -j, q.Len()-j)
}
}
}
}
func TestDequeBack(t *testing.T) {
q := new(Deque)
for i := 0; i < minCapacity+5; i++ {
q.PushBack(i)
if q.Back() != i {
t.Errorf("Back returned wrong value")
}
}
}
func TestCopy(t *testing.T) {
q := new(Deque)
a := make([]interface{}, minCapacity)
if q.Copy(a) != 0 {
t.Error("Copied wrong size, expected 0")
}
for i := 0; i < minCapacity/2; i++ {
q.PushBack(i)
q.PopFront()
}
for i := 0; i < minCapacity; i++ {
q.PushBack(i)
}
q.Copy(a)
for i := range a {
if a[i].(int) != i {
t.Error("Copy has wrong value at position", i)
}
}
a = []interface{}{}
if q.Copy(a) != 0 {
t.Error("Copied wrong size, expected 0")
}
a = make([]interface{}, q.Len()/2)
if q.Copy(a) != len(a) {
t.Error("Copied wrong size, expected", len(a))
}
a = make([]interface{}, q.Len()*2)
if q.Copy(a) != q.Len() {
t.Error("Copied wrong size", q.Len())
}
}
func TestRotate(t *testing.T) {
q := new(Deque)
for i := 0; i < 10; i++ {
q.PushBack(i)
}
a := make([]interface{}, q.Len())
for i := 0; i < q.Len(); i++ {
q.Copy(a)
x := i
for n := range a {
if a[n] != x {
t.Fatalf("a[%d] != %d after rotate and copy", n, x)
}
x++
if x == q.Len() {
x = 0
}
}
v := q.PopFront()
if v.(int) != i {
t.Fatal("wrong value during rotation")
}
q.PushBack(v)
}
for i := q.Len() - 1; i >= 0; i-- {
v := q.PopBack()
if v.(int) != i {
t.Fatal("wrong value during reverse rotation")
}
q.PushFront(v)
}
}
func TestDequeClear(t *testing.T) {
q := new(Deque)
for i := 0; i < 100; i++ {
q.PushBack(i)
}
if q.Len() != 100 {
t.Error("push: queue with 100 elements has length", q.Len())
}
cap := len(q.buf)
q.Clear()
if q.Len() != 0 {
t.Error("empty queue length not 0 after clear")
}
if len(q.buf) != cap {
t.Error("queue capacity changed after clear")
}
// Check that there are no remaining references after Clear()
for i := 0; i < len(q.buf); i++ {
if q.buf[i] != nil {
t.Error("queue has non-nil deleted elements after Clear()")
break
}
}
}
func TestPeekAtOutOfRangePanics(t *testing.T) {
q := new(Deque)
q.PushBack(1)
q.PushBack(2)
q.PushBack(3)
assertPanics(t, "should panic when negative index", func() {
q.PeekAt(-4)
})
assertPanics(t, "should panic when index greater than length", func() {
q.PeekAt(4)
})
}
func TestFrontBackOutOfRangePanics(t *testing.T) {
const msg = "should panic when peeking empty queue"
q := new(Deque)
assertPanics(t, msg, func() {
q.Front()
})
assertPanics(t, msg, func() {
q.Back()
})
q.PushBack(1)
q.PopFront()
assertPanics(t, msg, func() {
q.Front()
})
assertPanics(t, msg, func() {
q.Back()
})
}
func TestPopFrontOutOfRangePanics(t *testing.T) {
q := new(Deque)
assertPanics(t, "should panic when removing empty queue", func() {
q.PopFront()
})
q.PushBack(1)
q.PopFront()
assertPanics(t, "should panic when removing emptied queue", func() {
q.PopFront()
})
}
func TestPopBackOutOfRangePanics(t *testing.T) {
q := new(Deque)
assertPanics(t, "should panic when removing empty queue", func() {
q.PopBack()
})
q.PushBack(1)
q.PopBack()
assertPanics(t, "should panic when removing emptied queue", func() {
q.PopBack()
})
}
func assertPanics(t *testing.T, name string, f func()) {
defer func() {
if r := recover(); r == nil {
t.Errorf("%s: didn't panic as expected", name)
}
}()
f()
}
// Size (number of items) of Deque to use for benchmarks.
const size = minCapacity + (minCapacity / 2)
func BenchmarkPushFront(b *testing.B) {
var q Deque
for i := 0; i < b.N; i++ {
for n := 0; n < size; n++ {
q.PushFront(n)
}
}
}
func BenchmarkPushBack(b *testing.B) {
var q Deque
for i := 0; i < b.N; i++ {
for n := 0; n < size; n++ {
q.PushBack(n)
}
}
}
func BenchmarkSerial(b *testing.B) {
var q Deque
for i := 0; i < b.N; i++ {
for n := 0; n < size; n++ {
q.PushBack(i)
}
for n := 0; n < size; n++ {
x := q.Front()
if q.PopFront() != x {
panic("bad PopFront()")
}
}
}
}
func BenchmarkSerialReverse(b *testing.B) {
var q Deque
for i := 0; i < b.N; i++ {
for n := 0; n < size; n++ {
q.PushFront(i)
}
for n := 0; n < size; n++ {
x := q.Back()
if q.PopBack() != x {
panic("bad PopBack()")
}
}
}
}
func BenchmarkRotate(b *testing.B) {
q := new(Deque)
for i := 0; i < size; i++ {
q.PushBack(i)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
for j := 0; j < size; j++ {
v := q.PopFront()
q.PushBack(v)
}
}
}
func BenchmarkRotateReverse(b *testing.B) {
q := new(Deque)
for i := 0; i < size; i++ {
q.PushBack(i)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
for j := 0; j < size; j++ {
v := q.PopBack()
q.PushFront(v)
}
}
}
func BenchmarkDequePeekAt(b *testing.B) {
q := new(Deque)
for i := 0; i < size; i++ {
q.PushBack(i)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
for j := 0; j < size; j++ {
q.PeekAt(j)
}
}
}
func BenchmarkDequePushPop(b *testing.B) {
q := new(Deque)
for i := 0; i < b.N; i++ {
for n := 0; n < size; n++ {
q.PushBack(nil)
q.PopFront()
}
}
}

12
go.test.sh Executable file
View file

@ -0,0 +1,12 @@
#!/usr/bin/env bash
set -e
echo "" > coverage.txt
for d in $(go list ./... | grep -v vendor); do
go test -race -coverprofile=profile.out -covermode=atomic $d
if [ -f profile.out ]; then
cat profile.out >> coverage.txt
rm profile.out
fi
done