From 4e0a647678d800c673c0f760aeb3fad31f3ea0c7 Mon Sep 17 00:00:00 2001 From: Andrew Gillis Date: Mon, 23 Apr 2018 23:06:09 -0400 Subject: [PATCH] initial commit --- .gitignore | 26 +++ .travis.yml | 16 ++ README.md | 56 ++++++ deque.go | 193 +++++++++++++++++++ deque_test.go | 507 ++++++++++++++++++++++++++++++++++++++++++++++++++ go.test.sh | 12 ++ 6 files changed, 810 insertions(+) create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 README.md create mode 100644 deque.go create mode 100644 deque_test.go create mode 100755 go.test.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b33406f --- /dev/null +++ b/.gitignore @@ -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 diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..12d1d98 --- /dev/null +++ b/.travis.yml @@ -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) diff --git a/README.md b/README.md new file mode 100644 index 0000000..de8fbdc --- /dev/null +++ b/README.md @@ -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() +} +``` diff --git a/deque.go b/deque.go new file mode 100644 index 0000000..2d8933b --- /dev/null +++ b/deque.go @@ -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 +} diff --git a/deque_test.go b/deque_test.go new file mode 100644 index 0000000..83216cc --- /dev/null +++ b/deque_test.go @@ -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() + } + } +} diff --git a/go.test.sh b/go.test.sh new file mode 100755 index 0000000..34dbbfb --- /dev/null +++ b/go.test.sh @@ -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