Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add iterators #32

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 111 additions & 1 deletion 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 @@ -94,6 +97,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 @@ -115,6 +141,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 @@ -160,6 +209,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 Down Expand Up @@ -416,6 +509,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
229 changes: 225 additions & 4 deletions deque_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -384,14 +384,14 @@ func TestAt(t *testing.T) {
// Front to back.
for j := 0; j < q.Len(); j++ {
if q.At(j) != j {
t.Errorf("index %d doesn't contain %d", j, j)
t.Errorf("wrong item at index %d", j)
}
}

// Back to front
for j := 1; j <= 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)
for j := q.Len() - 1; j >= 0; j-- {
if q.At(j) != j {
t.Errorf("wrong item at index %d", j)
}
}
}
Expand Down Expand Up @@ -674,6 +674,227 @@ func TestSwap(t *testing.T) {
})
}

func TestIter(t *testing.T) {
var q Deque[int]

for range q.Iter() {
t.Fatal("iterated when empty")
}

q.Grow(50)
for i := 0; i < 50; i++ {
q.PushBack(i)
}

// Front to back.
expect := 0
for i, item := range q.Iter() {
if i != expect {
t.Fatalf("expected index %d, got %d", expect, i)
}
if item != i {
t.Errorf("index %d contains %d", i, item)
}
if i == 40 {
break
}
expect++
}

assertPanics(t, "Iter must panic when deque modified during iteration", func() {
for i, _ := range q.Iter() {
if i == 42 {
q.PushBack(51)
}
}
})
}

func TestRIter(t *testing.T) {
var q Deque[int]

for range q.RIter() {
t.Fatal("iterated when empty")
}

q.Grow(50)
for i := 0; i < 50; i++ {
q.PushBack(i)
}

// Back to fron
expect := 49
for i, item := range q.RIter() {
if i != expect {
t.Fatalf("expected index %d, got %d", expect, i)
}
if item != i {
t.Fatalf("index %d contains %d", i, item)
}
if i == 10 {
break
}
expect--
}

assertPanics(t, "RIter must panic when deque modified during iteration", func() {
for i, _ := range q.RIter() {
if i == 42 {
q.PushBack(51)
}
}
})
}

func TestIterPopBack(t *testing.T) {
var q Deque[int]
size := minCapacity * 5

q.Grow(size)
for i := 0; i < size; i++ {
q.PushBack(i)
}

last := q.Front()
var removed, lastRm int
for i := range q.IterPopBack() {
removed++
lastRm = i
}

if last != lastRm {
t.Fatal("did not expose expected item list")
}
if removed != size {
t.Fatal("wrong removed count")
}
if lastRm != 0 {
t.Fatal("wrong last item removed")
}
if q.Len() != 0 {
t.Error("q.Len() =", q.Len(), "expected 0")
}

q.Grow(size)
for i := 0; i < size; i++ {
q.PushBack(i)
}
last = q.Front()
removed = 0
for i := range q.IterPopBack() {
removed++
lastRm = i
}
if last != lastRm {
t.Fatal("did not expose expected item list")
}
if removed != size {
t.Fatal("wrong removed count, got", removed, " expected", size)
}
if q.Len() != 0 {
t.Error("q.Len() =", q.Len(), "expected 0")
}
for range q.IterPopBack() {
t.Fatal("iteration with 0 items")
}

for i := 0; i < 5; i++ {
q.PushBack(i)
}
c := q.Cap()
removed = 0
for range q.IterPopBack() {
removed++
}
if removed != 5 {
t.Fatal("wrong removed count")
}
if q.Cap() != c {
t.Fatal("unexpected capacity change")
}

for i := 0; i < 65; i++ {
q.PushBack(i)
}
c = q.Cap()
removed = 0
for range q.IterPopBack() {
removed++
if removed == 3 {
break
}
}
if q.Cap() != c {
t.Fatal("unexpected capacity change")
}
}

func TestIterPopFront(t *testing.T) {
var q Deque[int]
size := minCapacity * 5

q.Grow(size)
for i := 0; i < size; i++ {
q.PushBack(i)
}
last := q.Back()
var lastRm, removed int
for i := range q.IterPopFront() {
removed++
lastRm = i
}
if last != lastRm {
t.Fatal("did not expose expected item list")
}
if removed != size {
t.Fatal("wrong removed count")
}
if lastRm != size-1 {
t.Fatal("wrong last item removed")
}
if q.Len() != 0 {
t.Error("q.Len() =", q.Len(), "expected 0")
}
for range q.IterPopFront() {
t.Fatal("iteration with 0 items")
}

for i := 0; i < 5; i++ {
q.PushBack(i)
}
c := q.Cap()
removed = 0
for range q.IterPopFront() {
removed++
}
if removed != 5 {
t.Fatal("wrong removed count")
}
if q.Cap() != c {
t.Fatal("unexpected capacity change")
}

for i := 0; i < 65; i++ {
q.PushBack(i)
}
c = q.Cap()
removed = 0
for range q.IterPopFront() {
removed++
if removed == 3 {
break
}
}
for range q.IterPopFront() {
if q.Len() == 32 {
break
}
}
if q.Cap() == c {
t.Fatal("expected capacity change")
}
}

func TestFrontBackOutOfRangePanics(t *testing.T) {
const msg = "should panic when peeking empty queue"
var q Deque[int]
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module github.com/gammazero/deque

go 1.22
go 1.23
Loading