Skip to content

Commit

Permalink
Add iterators
Browse files Browse the repository at this point in the history
- Iter returns front-to-back go iterator
- RIter returns back-to-front go iterator
- IterPopFront returns go iterator that removes items from front of Deque
- IterPopBack returns go iterator that removes items from back fo Deque

Using iterators to operate on sequences of items in Deque can avoid unnecessary range checks and multiple resizes.
  • Loading branch information
gammazero committed Nov 14, 2024
1 parent 2d5497f commit 86f361b
Show file tree
Hide file tree
Showing 3 changed files with 338 additions and 7 deletions.
114 changes: 112 additions & 2 deletions deque.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package deque

import "fmt"
import (
"fmt"
"iter"
)

// minCapacity is the smallest capacity that deque may have. Must be power of 2
// for bitwise modulus: x % n == x & (n - 1).
Expand Down Expand Up @@ -113,6 +116,29 @@ func (q *Deque[T]) PopFront() T {
return ret
}

// IterPopFront returns an iterator the iteratively removes items from the
// Front of the deque. This is more efficient than removing items one at a time
// because it avoids intermediate resizing. If a resize is necessary, only one
// is done when iteration ends.
func (q *Deque[T]) IterPopFront() iter.Seq[T] {
return func(yield func(T) bool) {
if q.Len() == 0 {
return
}
var zero T
for q.count != 0 {
ret := q.buf[q.head]
q.buf[q.head] = zero
q.head = q.next(q.head)
q.count--
if !yield(ret) {
break
}
}
q.shrinkToFit()
}
}

// 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.
Expand All @@ -134,6 +160,29 @@ func (q *Deque[T]) PopBack() T {
return ret
}

// IterPopBack returns an iterator the iteratively removes items from the back
// of the deque. This is more efficient than removing items one at a time
// because it avoids intermediate resizing. If a resize is necessary, only one
// is done when iteration ends.
func (q *Deque[T]) IterPopBack() iter.Seq[T] {
return func(yield func(T) bool) {
if q.Len() == 0 {
return
}
var zero T
for q.count != 0 {
q.tail = q.prev(q.tail)
ret := q.buf[q.tail]
q.buf[q.tail] = zero
q.count--
if !yield(ret) {
break
}
}
q.shrinkToFit()
}
}

// 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[T]) Front() T {
Expand Down Expand Up @@ -179,6 +228,50 @@ func (q *Deque[T]) Set(i int, item T) {
q.buf[(q.head+i)&(len(q.buf)-1)] = item
}

// Iter returns a go iterator to range over all items in the Deque, yielding
// the index of each item and the item, from front to back. Modification of
// Deque during iteration panics.
func (q *Deque[T]) Iter() iter.Seq2[int, T] {
return func(yield func(int, T) bool) {
if q.Len() == 0 {
return
}
count := q.count
head := q.head
for i := 0; i < count; i++ {
if q.count != count {
panic("deque: modified during iteration")
}
if !yield(i, q.buf[head]) {
return
}
head = q.next(head)
}
}
}

// RIter returns a go iterator to range over all items in the Deque, yielding
// the index of each item and the item, from back to front. Modification of
// Deque during iteration panics.
func (q *Deque[T]) RIter() iter.Seq2[int, T] {
return func(yield func(int, T) bool) {
if q.Len() == 0 {
return
}
count := q.count
tail := q.tail
for i := count - 1; i >= 0; i-- {
if q.count != count {
panic("deque: modified during iteration")
}
tail = q.prev(tail)
if !yield(i, q.buf[tail]) {
return
}
}
}
}

// Clear removes all elements from the queue, but retains the current capacity.
// This is useful when repeatedly reusing the queue at high frequency to avoid
// GC during reuse. The queue will not be resized smaller as long as items are
Expand All @@ -196,7 +289,7 @@ func (q *Deque[T]) Clear() {
q.count = 0
}

// Grow grows the deque's capacity, if necessary, to guarantee space for
// Grow grows the Deque's capacity, if necessary, to guarantee space for
// another n items. After Grow(n), at least n items can be written to the
// buffer without another allocation. If n is negative, Grow will panic.
func (q *Deque[T]) Grow(n int) {
Expand Down Expand Up @@ -435,6 +528,23 @@ func (q *Deque[T]) shrinkIfExcess() {
}
}

func (q *Deque[T]) shrinkToFit() {
if len(q.buf) > q.minCap && (q.count<<2) <= len(q.buf) {
if q.count == 0 {
q.head = 0
q.tail = 0
q.buf = make([]T, minCapacity)
return
}

c := minCapacity
for c < q.count {
c <<= 1
}
q.resize(c)
}
}

// 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.
Expand Down
Loading

0 comments on commit 86f361b

Please sign in to comment.