From d5d0c9a4372cce7abb79944169fe21be114d062e Mon Sep 17 00:00:00 2001 From: Andrew Gillis Date: Mon, 30 May 2022 10:51:57 -0700 Subject: [PATCH] Use generics to define Deque instance for a type (#23) * Use generics to define Deque instance for a type * Require go1.18 --- .github/workflows/go.yml | 2 +- README.md | 10 ++- deque.go | 92 +++++++++++---------- deque_test.go | 169 ++++++++++++++++++++------------------- doc.go | 29 ++++--- go.mod | 2 +- 6 files changed, 164 insertions(+), 140 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index b650d7a..d17460d 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -16,7 +16,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v2 with: - go-version: 1.16 + go-version: 1.18 - name: Vet run: go vet ./... diff --git a/README.md b/README.md index 97a2663..78f99c4 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,14 @@ For maximum speed, this deque implementation leaves concurrency safety up to the Since it is OK for the deque to contain a `nil` value, it is necessary to either panic or return a second boolean value to indicate the deque is empty, when reading or removing an element. This deque panics when reading from an empty deque. This is a run-time check to help catch programming errors, which may be missed if a second return value is ignored. Simply check `Deque.Len()` before reading from the deque. +## Generics + +Deque uses generics to create a Deque that contains items of the type specified. To create a Deque that holds a specific type, provide a type argument to New or with the variable declaration. For example: +```go + stringDeque := deque.New[string]() + var intDeque deque.Deque[int] +``` + ## Example ```go @@ -43,7 +51,7 @@ import ( ) func main() { - var q deque.Deque + var q deque.Deque[string] q.PushBack("foo") q.PushBack("bar") q.PushBack("baz") diff --git a/deque.go b/deque.go index 3fd9329..a120ccf 100644 --- a/deque.go +++ b/deque.go @@ -4,9 +4,10 @@ package deque // 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{} +// Deque represents a single instance of the deque data structure. A Deque +// instance contains items of the type sepcified by the type argument. +type Deque[T any] struct { + buf []T head int tail int count int @@ -14,18 +15,22 @@ type Deque struct { } // New creates a new Deque, optionally setting the current and minimum capacity -// when non-zero values are given for these. +// when non-zero values are given for these. The Deque instance returns +// operates on items of the type specified by the type argument. For example, +// to create a Deque that contains strings, // -// To create a Deque with capacity to store 2048 items without resizing, and +// stringDeque := deque.New[string]() +// +// To create a Deque with capacity to store 2048 ints without resizing, and // that will not resize below space for 32 items when removing items: -// d := deque.New(2048, 32) +// d := deque.New[int](2048, 32) // // To create a Deque that has not yet allocated memory, but after it does will // never resize to have space for less than 64 items: -// d := deque.New(0, 64) +// d := deque.New[int](0, 64) // -// Note that any values supplied here are rounded up to the nearest power of 2. -func New(size ...int) *Deque { +// Any size values supplied here are rounded up to the nearest power of 2. +func New[T any](size ...int) *Deque[T] { var capacity, minimum int if len(size) >= 1 { capacity = size[0] @@ -39,23 +44,23 @@ func New(size ...int) *Deque { minCap <<= 1 } - var buf []interface{} + var buf []T if capacity != 0 { bufSize := minCap for bufSize < capacity { bufSize <<= 1 } - buf = make([]interface{}, bufSize) + buf = make([]T, bufSize) } - return &Deque{ + return &Deque[T]{ buf: buf, minCap: minCap, } } // Cap returns the current capacity of the Deque. If q is nil, q.Cap() is zero. -func (q *Deque) Cap() int { +func (q *Deque[T]) Cap() int { if q == nil { return 0 } @@ -64,7 +69,7 @@ func (q *Deque) Cap() int { // Len returns the number of elements currently stored in the queue. If q is // nil, q.Len() is zero. -func (q *Deque) Len() int { +func (q *Deque[T]) Len() int { if q == nil { return 0 } @@ -74,7 +79,7 @@ func (q *Deque) Len() int { // 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{}) { +func (q *Deque[T]) PushBack(elem T) { q.growIfFull() q.buf[q.tail] = elem @@ -84,7 +89,7 @@ func (q *Deque) PushBack(elem interface{}) { } // PushFront prepends an element to the front of the queue. -func (q *Deque) PushFront(elem interface{}) { +func (q *Deque[T]) PushFront(elem T) { q.growIfFull() // Calculate new head position. @@ -96,12 +101,13 @@ func (q *Deque) PushFront(elem interface{}) { // 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{} { +func (q *Deque[T]) PopFront() T { if q.count <= 0 { panic("deque: PopFront() called on empty queue") } ret := q.buf[q.head] - q.buf[q.head] = nil + var zero T + q.buf[q.head] = zero // Calculate new head position. q.head = q.next(q.head) q.count-- @@ -113,7 +119,7 @@ func (q *Deque) PopFront() interface{} { // 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{} { +func (q *Deque[T]) PopBack() T { if q.count <= 0 { panic("deque: PopBack() called on empty queue") } @@ -123,7 +129,8 @@ func (q *Deque) PopBack() interface{} { // Remove value at tail. ret := q.buf[q.tail] - q.buf[q.tail] = nil + var zero T + q.buf[q.tail] = zero q.count-- q.shrinkIfExcess() @@ -133,7 +140,7 @@ func (q *Deque) PopBack() interface{} { // 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{} { +func (q *Deque[T]) Front() T { if q.count <= 0 { panic("deque: Front() called when empty") } @@ -142,7 +149,7 @@ func (q *Deque) Front() interface{} { // 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{} { +func (q *Deque[T]) Back() T { if q.count <= 0 { panic("deque: Back() called when empty") } @@ -161,7 +168,7 @@ func (q *Deque) Back() interface{} { // case of a fixed-size circular log buffer: A new entry is pushed onto one end // and when full the oldest is popped from the other end. All the log entries // in the buffer must be readable without altering the buffer contents. -func (q *Deque) At(i int) interface{} { +func (q *Deque[T]) At(i int) T { if i < 0 || i >= q.count { panic("deque: At() called with index out of range") } @@ -172,7 +179,7 @@ func (q *Deque) At(i int) interface{} { // Set puts the element at index i in the queue. Set shares the same purpose // than At() but perform the opposite operation. The index i is the same index // defined by At(). If the index is invalid, the call panics. -func (q *Deque) Set(i int, elem interface{}) { +func (q *Deque[T]) Set(i int, elem T) { if i < 0 || i >= q.count { panic("deque: Set() called with index out of range") } @@ -185,11 +192,12 @@ func (q *Deque) Set(i int, elem interface{}) { // GC during reuse. The queue will not be resized smaller as long as items are // only added. Only when items are removed is the queue subject to getting // resized smaller. -func (q *Deque) Clear() { +func (q *Deque[T]) Clear() { // bitwise modulus modBits := len(q.buf) - 1 + var zero T for h := q.head; h != q.tail; h = (h + 1) & modBits { - q.buf[h] = nil + q.buf[h] = zero } q.head = 0 q.tail = 0 @@ -200,7 +208,7 @@ func (q *Deque) Clear() { // back-to-front. Having Deque provide Rotate() avoids resizing that could // happen if implementing rotation using only Pop and Push methods. If q.Len() // is one or less, or q is nil, then Rotate does nothing. -func (q *Deque) Rotate(n int) { +func (q *Deque[T]) Rotate(n int) { if q.Len() <= 1 { return } @@ -219,6 +227,8 @@ func (q *Deque) Rotate(n int) { return } + var zero T + if n < 0 { // Rotate back to front. for ; n < 0; n++ { @@ -227,7 +237,7 @@ func (q *Deque) Rotate(n int) { q.tail = (q.tail - 1) & modBits // Put tail value at head and remove value at tail. q.buf[q.head] = q.buf[q.tail] - q.buf[q.tail] = nil + q.buf[q.tail] = zero } return } @@ -236,7 +246,7 @@ func (q *Deque) Rotate(n int) { for ; n > 0; n-- { // Put head value at tail and remove value at head. q.buf[q.tail] = q.buf[q.head] - q.buf[q.head] = nil + q.buf[q.head] = zero // Calculate new head and tail using bitwise modulus. q.head = (q.head + 1) & modBits q.tail = (q.tail + 1) & modBits @@ -246,7 +256,7 @@ func (q *Deque) Rotate(n int) { // Index returns the index into the Deque of the first item satisfying f(item), // or -1 if none do. If q is nil, then -1 is always returned. Search is linear // starting with index 0. -func (q *Deque) Index(f func(interface{}) bool) int { +func (q *Deque[T]) Index(f func(T) bool) int { if q.Len() > 0 { modBits := len(q.buf) - 1 for i := 0; i < q.count; i++ { @@ -261,7 +271,7 @@ func (q *Deque) Index(f func(interface{}) bool) int { // RIndex is the same as Index, but searches from Back to Front. The index // returned is from Front to Back, where index 0 is the index of the item // returned by Front(). -func (q *Deque) RIndex(f func(interface{}) bool) int { +func (q *Deque[T]) RIndex(f func(T) bool) int { if q.Len() > 0 { modBits := len(q.buf) - 1 for i := q.count - 1; i >= 0; i-- { @@ -282,7 +292,7 @@ func (q *Deque) RIndex(f func(interface{}) bool) int { // not for operations in the the middle. Complexity of this function is // constant plus linear in the lesser of the distances between the index and // either of the ends of the queue. -func (q *Deque) Insert(at int, item interface{}) { +func (q *Deque[T]) Insert(at int, item T) { if at < 0 || at > q.count { panic("deque: Insert() called with index out of range") } @@ -315,7 +325,7 @@ func (q *Deque) Insert(at int, item interface{}) { // not for operations in the the middle. Complexity of this function is // constant plus linear in the lesser of the distances between the index and // either of the ends of the queue. -func (q *Deque) Remove(at int) interface{} { +func (q *Deque[T]) Remove(at int) T { if at < 0 || at >= q.Len() { panic("deque: Remove() called with index out of range") } @@ -345,7 +355,7 @@ func (q *Deque) Remove(at int) interface{} { // // Setting a larger minimum capacity may be used to prevent resizing when the // number of stored items changes frequently across a wide range. -func (q *Deque) SetMinCapacity(minCapacityExp uint) { +func (q *Deque[T]) SetMinCapacity(minCapacityExp uint) { if 1< minCapacity { q.minCap = 1 << minCapacityExp } else { @@ -354,17 +364,17 @@ func (q *Deque) SetMinCapacity(minCapacityExp uint) { } // prev returns the previous buffer position wrapping around buffer. -func (q *Deque) prev(i int) int { +func (q *Deque[T]) prev(i int) int { return (i - 1) & (len(q.buf) - 1) // bitwise modulus } // next returns the next buffer position wrapping around buffer. -func (q *Deque) next(i int) int { +func (q *Deque[T]) next(i int) int { return (i + 1) & (len(q.buf) - 1) // bitwise modulus } // growIfFull resizes up if the buffer is full. -func (q *Deque) growIfFull() { +func (q *Deque[T]) growIfFull() { if q.count != len(q.buf) { return } @@ -372,14 +382,14 @@ func (q *Deque) growIfFull() { if q.minCap == 0 { q.minCap = minCapacity } - q.buf = make([]interface{}, q.minCap) + q.buf = make([]T, q.minCap) return } q.resize() } // shrinkIfExcess resize down if the buffer 1/4 full. -func (q *Deque) shrinkIfExcess() { +func (q *Deque[T]) shrinkIfExcess() { if len(q.buf) > q.minCap && (q.count<<2) == len(q.buf) { q.resize() } @@ -388,8 +398,8 @@ func (q *Deque) shrinkIfExcess() { // resize resizes the deque to fit exactly twice its current contents. This is // used to grow the queue when it is full, and also to shrink it when it is // only a quarter full. -func (q *Deque) resize() { - newBuf := make([]interface{}, q.count<<1) +func (q *Deque[T]) resize() { + newBuf := make([]T, q.count<<1) if q.tail > q.head { copy(newBuf, q.buf[q.head:q.tail]) } else { diff --git a/deque_test.go b/deque_test.go index 80ccf4a..21ad8fb 100644 --- a/deque_test.go +++ b/deque_test.go @@ -7,20 +7,20 @@ import ( ) func TestEmpty(t *testing.T) { - var q Deque + q := New[string]() if q.Len() != 0 { t.Error("q.Len() =", q.Len(), "expect 0") } if q.Cap() != 0 { t.Error("expected q.Cap() == 0") } - idx := q.Index(func(item interface{}) bool { + idx := q.Index(func(item string) bool { return true }) if idx != -1 { t.Error("should return -1 index for nil deque") } - idx = q.RIndex(func(item interface{}) bool { + idx = q.RIndex(func(item string) bool { return true }) if idx != -1 { @@ -29,7 +29,7 @@ func TestEmpty(t *testing.T) { } func TestNil(t *testing.T) { - var q *Deque + var q *Deque[int] if q.Len() != 0 { t.Error("expected q.Len() == 0") } @@ -37,13 +37,13 @@ func TestNil(t *testing.T) { t.Error("expected q.Cap() == 0") } q.Rotate(5) - idx := q.Index(func(item interface{}) bool { + idx := q.Index(func(item int) bool { return true }) if idx != -1 { t.Error("should return -1 index for nil deque") } - idx = q.RIndex(func(item interface{}) bool { + idx = q.RIndex(func(item int) bool { return true }) if idx != -1 { @@ -52,7 +52,7 @@ func TestNil(t *testing.T) { } func TestFrontBack(t *testing.T) { - var q Deque + var q Deque[string] q.PushBack("foo") q.PushBack("bar") q.PushBack("baz") @@ -85,7 +85,7 @@ func TestFrontBack(t *testing.T) { } func TestGrowShrinkBack(t *testing.T) { - var q Deque + var q Deque[int] size := minCapacity * 2 for i := 0; i < size; i++ { @@ -115,7 +115,7 @@ func TestGrowShrinkBack(t *testing.T) { } func TestGrowShrinkFront(t *testing.T) { - var q Deque + var q Deque[int] size := minCapacity * 2 for i := 0; i < size; i++ { @@ -145,16 +145,16 @@ func TestGrowShrinkFront(t *testing.T) { } func TestSimple(t *testing.T) { - var q Deque + var q Deque[int] for i := 0; i < minCapacity; i++ { q.PushBack(i) } if q.Front() != 0 { - t.Fatalf("expected 0 at front, got %d", q.Front().(int)) + t.Fatalf("expected 0 at front, got %d", q.Front()) } if q.Back() != minCapacity-1 { - t.Fatalf("expected %d at back, got %d", minCapacity-1, q.Back().(int)) + t.Fatalf("expected %d at back, got %d", minCapacity-1, q.Back()) } for i := 0; i < minCapacity; i++ { @@ -180,7 +180,7 @@ func TestSimple(t *testing.T) { } func TestBufferWrap(t *testing.T) { - var q Deque + var q Deque[int] for i := 0; i < minCapacity; i++ { q.PushBack(i) @@ -192,7 +192,7 @@ func TestBufferWrap(t *testing.T) { } for i := 0; i < minCapacity; i++ { - if q.Front().(int) != i+3 { + if q.Front() != i+3 { t.Error("peek", i, "had value", q.Front()) } q.PopFront() @@ -200,7 +200,7 @@ func TestBufferWrap(t *testing.T) { } func TestBufferWrapReverse(t *testing.T) { - var q Deque + var q Deque[int] for i := 0; i < minCapacity; i++ { q.PushFront(i) @@ -211,7 +211,7 @@ func TestBufferWrapReverse(t *testing.T) { } for i := 0; i < minCapacity; i++ { - if q.Back().(int) != i+3 { + if q.Back() != i+3 { t.Error("peek", i, "had value", q.Front()) } q.PopBack() @@ -219,7 +219,7 @@ func TestBufferWrapReverse(t *testing.T) { } func TestLen(t *testing.T) { - var q Deque + var q Deque[int] if q.Len() != 0 { t.Error("empty queue length not 0") @@ -240,7 +240,7 @@ func TestLen(t *testing.T) { } func TestBack(t *testing.T) { - var q Deque + var q Deque[int] for i := 0; i < minCapacity+5; i++ { q.PushBack(i) @@ -252,7 +252,7 @@ func TestBack(t *testing.T) { func TestNew(t *testing.T) { minCap := 64 - q := New(0, minCap) + q := New[string](0, minCap) if q.Cap() != 0 { t.Fatal("should not have allowcated mem yet") } @@ -266,7 +266,7 @@ func TestNew(t *testing.T) { } curCap := 128 - q = New(curCap, minCap) + q = New[string](curCap, minCap) if q.Cap() != curCap { t.Fatalf("Cap() should return %d, got %d", curCap, q.Cap()) } @@ -280,7 +280,7 @@ func TestNew(t *testing.T) { } func checkRotate(t *testing.T, size int) { - var q Deque + var q Deque[int] for i := 0; i < size; i++ { q.PushBack(i) } @@ -297,13 +297,13 @@ func checkRotate(t *testing.T, size int) { } } q.Rotate(1) - if q.Back().(int) != i { + if q.Back() != i { t.Fatal("wrong value during rotation") } } for i := q.Len() - 1; i >= 0; i-- { q.Rotate(-1) - if q.Front().(int) != i { + if q.Front() != i { t.Fatal("wrong value during reverse rotation") } } @@ -314,7 +314,7 @@ func TestRotate(t *testing.T) { checkRotate(t, minCapacity) checkRotate(t, minCapacity+minCapacity/2) - var q Deque + var q Deque[int] for i := 0; i < 10; i++ { q.PushBack(i) } @@ -339,7 +339,7 @@ func TestRotate(t *testing.T) { } func TestAt(t *testing.T) { - var q Deque + var q Deque[int] for i := 0; i < 1000; i++ { q.PushBack(i) @@ -347,21 +347,21 @@ func TestAt(t *testing.T) { // Front to back. for j := 0; j < q.Len(); j++ { - if q.At(j).(int) != j { + if q.At(j) != j { t.Errorf("index %d doesn't contain %d", j, j) } } // Back to front for j := 1; j <= q.Len(); j++ { - if q.At(q.Len()-j).(int) != q.Len()-j { + if q.At(q.Len()-j) != q.Len()-j { t.Errorf("index %d doesn't contain %d", q.Len()-j, q.Len()-j) } } } func TestSet(t *testing.T) { - var q Deque + var q Deque[int] for i := 0; i < 1000; i++ { q.PushBack(i) @@ -370,14 +370,14 @@ func TestSet(t *testing.T) { // Front to back. for j := 0; j < q.Len(); j++ { - if q.At(j).(int) != j+50 { + if q.At(j) != j+50 { t.Errorf("index %d doesn't contain %d", j, j+50) } } } func TestClear(t *testing.T) { - var q Deque + var q Deque[int] for i := 0; i < 100; i++ { q.PushBack(i) @@ -396,7 +396,7 @@ func TestClear(t *testing.T) { // Check that there are no remaining references after Clear() for i := 0; i < len(q.buf); i++ { - if q.buf[i] != nil { + if q.buf[i] != 0 { t.Error("queue has non-nil deleted elements after Clear()") break } @@ -404,25 +404,25 @@ func TestClear(t *testing.T) { } func TestIndex(t *testing.T) { - var q Deque + var q Deque[rune] for _, x := range "Hello, 世界" { q.PushBack(x) } - idx := q.Index(func(item interface{}) bool { - c := item.(rune) + idx := q.Index(func(item rune) bool { + c := item return unicode.Is(unicode.Han, c) }) if idx != 7 { t.Fatal("Expected index 7, got", idx) } - idx = q.Index(func(item interface{}) bool { - c := item.(rune) + idx = q.Index(func(item rune) bool { + c := item return c == 'H' }) if idx != 0 { t.Fatal("Expected index 0, got", idx) } - idx = q.Index(func(item interface{}) bool { + idx = q.Index(func(item rune) bool { return false }) if idx != -1 { @@ -431,25 +431,25 @@ func TestIndex(t *testing.T) { } func TestRIndex(t *testing.T) { - var q Deque + var q Deque[rune] for _, x := range "Hello, 世界" { q.PushBack(x) } - idx := q.RIndex(func(item interface{}) bool { - c := item.(rune) + idx := q.RIndex(func(item rune) bool { + c := item return unicode.Is(unicode.Han, c) }) if idx != 8 { t.Fatal("Expected index 8, got", idx) } - idx = q.RIndex(func(item interface{}) bool { - c := item.(rune) + idx = q.RIndex(func(item rune) bool { + c := item return c == 'H' }) if idx != 0 { t.Fatal("Expected index 0, got", idx) } - idx = q.RIndex(func(item interface{}) bool { + idx = q.RIndex(func(item rune) bool { return false }) if idx != -1 { @@ -458,7 +458,7 @@ func TestRIndex(t *testing.T) { } func TestInsert(t *testing.T) { - q := new(Deque) + q := new(Deque[rune]) for _, x := range "ABCDEFG" { q.PushBack(x) } @@ -488,72 +488,73 @@ func TestInsert(t *testing.T) { } } - q = New(16) + qs := New[string](16) - for i := 0; i < q.Cap(); i++ { - q.PushBack(fmt.Sprint(i)) + for i := 0; i < qs.Cap(); i++ { + qs.PushBack(fmt.Sprint(i)) } // deque: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // buffer: [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15] - for i := 0; i < q.Cap()/2; i++ { - q.PopFront() + for i := 0; i < qs.Cap()/2; i++ { + qs.PopFront() } // deque: 8 9 10 11 12 13 14 15 // buffer: [_,_,_,_,_,_,_,_,8,9,10,11,12,13,14,15] - for i := 0; i < q.Cap()/4; i++ { - q.PushBack(fmt.Sprint(q.Cap() + i)) + for i := 0; i < qs.Cap()/4; i++ { + qs.PushBack(fmt.Sprint(qs.Cap() + i)) } // deque: 8 9 10 11 12 13 14 15 16 17 18 19 // buffer: [16,17,18,19,_,_,_,_,8,9,10,11,12,13,14,15] - at := q.Len() - 2 - q.Insert(at, "x") + at := qs.Len() - 2 + qs.Insert(at, "x") // deque: 8 9 10 11 12 13 14 15 16 17 x 18 19 // buffer: [16,17,x,18,19,_,_,_,8,9,10,11,12,13,14,15] - if q.At(at) != "x" { + if qs.At(at) != "x" { t.Error("expected x at position", at) } - if q.At(at) != "x" { + if qs.At(at) != "x" { t.Error("expected x at position", at) } - q.Insert(2, "y") + qs.Insert(2, "y") // deque: 8 9 y 10 11 12 13 14 15 16 17 x 18 19 // buffer: [16,17,x,18,19,_,_,8,9,y,10,11,12,13,14,15] - if q.At(2) != "y" { + if qs.At(2) != "y" { t.Error("expected y at position 2") } - if q.At(at+1) != "x" { + if qs.At(at+1) != "x" { t.Error("expected x at position 5") } - q.Insert(0, "b") + qs.Insert(0, "b") // deque: b 8 9 y 10 11 12 13 14 15 16 17 x 18 19 // buffer: [16,17,x,18,19,_,b,8,9,y,10,11,12,13,14,15] - if q.Front() != "b" { - t.Error("expected b inserted at front, got", q.Front()) + if qs.Front() != "b" { + t.Error("expected b inserted at front, got", qs.Front()) } - q.Insert(q.Len(), "e") - if q.Cap() != q.Len() { + qs.Insert(qs.Len(), "e") + if qs.Cap() != qs.Len() { t.Fatal("Expected full buffer") } // deque: b 8 9 y 10 11 12 13 14 15 16 17 x 18 19 e // buffer: [16,17,x,18,19,e,b,8,9,y,10,11,12,13,14,15] for i, x := range []string{"16", "17", "x", "18", "19", "e", "b", "8", "9", "y", "10", "11", "12", "13", "14", "15"} { - if q.buf[i] != x { + if qs.buf[i] != x { t.Error("expected", x, "at buffer position", i) } } for i, x := range []string{"b", "8", "9", "y", "10", "11", "12", "13", "14", "15", "16", "17", "x", "18", "19", "e"} { - if q.PopFront() != x { - t.Error("expected", x, "at position", i) + if qs.Front() != x { + t.Error("expected", x, "at position", i, "got", qs.Front()) } + qs.PopFront() } } func TestRemove(t *testing.T) { - q := new(Deque) + q := new(Deque[rune]) for _, x := range "ABCDEFG" { q.PushBack(x) } @@ -590,7 +591,7 @@ func TestRemove(t *testing.T) { func TestFrontBackOutOfRangePanics(t *testing.T) { const msg = "should panic when peeking empty queue" - var q Deque + var q Deque[int] assertPanics(t, msg, func() { q.Front() }) @@ -610,7 +611,7 @@ func TestFrontBackOutOfRangePanics(t *testing.T) { } func TestPopFrontOutOfRangePanics(t *testing.T) { - var q Deque + var q Deque[int] assertPanics(t, "should panic when removing empty queue", func() { q.PopFront() @@ -625,7 +626,7 @@ func TestPopFrontOutOfRangePanics(t *testing.T) { } func TestPopBackOutOfRangePanics(t *testing.T) { - var q Deque + var q Deque[int] assertPanics(t, "should panic when removing empty queue", func() { q.PopBack() @@ -640,7 +641,7 @@ func TestPopBackOutOfRangePanics(t *testing.T) { } func TestAtOutOfRangePanics(t *testing.T) { - var q Deque + var q Deque[int] q.PushBack(1) q.PushBack(2) @@ -656,7 +657,7 @@ func TestAtOutOfRangePanics(t *testing.T) { } func TestSetOutOfRangePanics(t *testing.T) { - var q Deque + var q Deque[int] q.PushBack(1) q.PushBack(2) @@ -672,7 +673,7 @@ func TestSetOutOfRangePanics(t *testing.T) { } func TestInsertOutOfRangePanics(t *testing.T) { - q := new(Deque) + q := new(Deque[string]) assertPanics(t, "should panic when inserting out of range", func() { q.Insert(1, "X") @@ -690,7 +691,7 @@ func TestInsertOutOfRangePanics(t *testing.T) { } func TestRemoveOutOfRangePanics(t *testing.T) { - q := new(Deque) + q := new(Deque[string]) assertPanics(t, "should panic when removing from empty queue", func() { q.Remove(0) @@ -708,7 +709,7 @@ func TestRemoveOutOfRangePanics(t *testing.T) { } func TestSetMinCapacity(t *testing.T) { - var q Deque + var q Deque[string] exp := uint(8) q.SetMinCapacity(exp) q.PushBack("A") @@ -742,21 +743,21 @@ func assertPanics(t *testing.T, name string, f func()) { } func BenchmarkPushFront(b *testing.B) { - var q Deque + var q Deque[int] for i := 0; i < b.N; i++ { q.PushFront(i) } } func BenchmarkPushBack(b *testing.B) { - var q Deque + var q Deque[int] for i := 0; i < b.N; i++ { q.PushBack(i) } } func BenchmarkSerial(b *testing.B) { - var q Deque + var q Deque[int] for i := 0; i < b.N; i++ { q.PushBack(i) } @@ -766,7 +767,7 @@ func BenchmarkSerial(b *testing.B) { } func BenchmarkSerialReverse(b *testing.B) { - var q Deque + var q Deque[int] for i := 0; i < b.N; i++ { q.PushFront(i) } @@ -776,7 +777,7 @@ func BenchmarkSerialReverse(b *testing.B) { } func BenchmarkRotate(b *testing.B) { - q := new(Deque) + q := new(Deque[int]) for i := 0; i < b.N; i++ { q.PushBack(i) } @@ -788,7 +789,7 @@ func BenchmarkRotate(b *testing.B) { } func BenchmarkInsert(b *testing.B) { - q := new(Deque) + q := new(Deque[int]) for i := 0; i < b.N; i++ { q.PushBack(i) } @@ -799,7 +800,7 @@ func BenchmarkInsert(b *testing.B) { } func BenchmarkRemove(b *testing.B) { - q := new(Deque) + q := new(Deque[int]) for i := 0; i < b.N; i++ { q.PushBack(i) } @@ -810,7 +811,7 @@ func BenchmarkRemove(b *testing.B) { } func BenchmarkYoyo(b *testing.B) { - var q Deque + var q Deque[int] for i := 0; i < b.N; i++ { for j := 0; j < 65536; j++ { q.PushBack(j) @@ -822,7 +823,7 @@ func BenchmarkYoyo(b *testing.B) { } func BenchmarkYoyoFixed(b *testing.B) { - var q Deque + var q Deque[int] q.SetMinCapacity(16) for i := 0; i < b.N; i++ { for j := 0; j < 65536; j++ { diff --git a/doc.go b/doc.go index c9647f9..3e6832b 100644 --- a/doc.go +++ b/doc.go @@ -3,16 +3,15 @@ Package deque provides a fast ring-buffer deque (double-ended queue) implementation. Deque generalizes a queue and a stack, to efficiently add and remove items at -either end with O(1) performance. Queue (FIFO) operations are supported using -PushBack() and PopFront(). Stack (LIFO) operations are supported using +either end with O(1) performance. Queue (FIFO) operations are supported using +PushBack() and PopFront(). Stack (LIFO) operations are supported using PushBack() and PopBack(). Ring-buffer Performance -The ring-buffer automatically resizes by -powers of two, growing when additional capacity is needed and shrinking when -only a quarter of the capacity is used, and uses bitwise arithmetic for all -calculations. +The ring-buffer automatically resizes by powers of two, growing when additional +capacity is needed and shrinking when only a quarter of the capacity is used, +and uses 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 @@ -23,12 +22,18 @@ the application to provide, however the application chooses, if needed at all. Reading Empty Deque -Since it is OK for the deque to contain a nil value, it is necessary to either -panic or return a second boolean value to indicate the deque is empty, when -reading or removing an element. This deque panics when reading from an empty -deque. This is a run-time check to help catch programming errors, which may be -missed if a second return value is ignored. Simply check Deque.Len() before -reading from the deque. +Since it is OK for the deque to contain the zero-value of an item, it is +necessary to either panic or return a second boolean value to indicate the +deque is empty, when reading or removing an element. This deque panics when +reading from an empty deque. This is a run-time check to help catch programming +errors, which may be missed if a second return value is ignored. Simply check +Deque.Len() before reading from the deque. + +Generics + +Deque uses generics to create a Deque that contains items of the type +specified. To create a Deque that holds a specific type, provide a type +argument to New or with the variable declaration. */ package deque diff --git a/go.mod b/go.mod index b440602..d17d543 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ module github.com/gammazero/deque -go 1.15 +go 1.18