Compare commits

...

4 Commits

Author SHA1 Message Date
9a15ebed25 Add iterators for mq and npq.
All checks were successful
Build & Test / Main (push) Successful in 13s
2024-09-07 02:10:40 -07:00
c5dde2ab43 Update golangci-lint in build workflow.
All checks were successful
Build & Test / Main (push) Successful in 7s
2024-08-22 00:59:57 -07:00
38ef55e463 Update Go in build workflow.
Some checks failed
Build & Test / Main (push) Failing after 11s
2024-08-22 00:59:08 -07:00
87212466ae Add iterators for a couple queues.
Some checks failed
Build & Test / Main (push) Failing after 5s
2024-08-22 00:53:48 -07:00
10 changed files with 199 additions and 3 deletions

View File

@ -17,12 +17,12 @@ jobs:
name: Set up Go
uses: actions/setup-go@v4
with:
go-version: 1.21.5
go-version: 1.23.0
-
name: Run linter
uses: golangci/golangci-lint-action@v3
with:
version: v1.54
version: v1.60.2
-
name: Run tests
run: go test ./...

2
go.mod
View File

@ -1,5 +1,5 @@
module gogs.humancabbage.net/sam/priorityq
go 1.20
go 1.23
require golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2

View File

@ -28,6 +28,7 @@
package mq
import (
"iter"
"sync"
"gogs.humancabbage.net/sam/priorityq"
@ -199,3 +200,18 @@ func (s *state[T]) trySend(value T, buf *queue.Q[T]) error {
s.canRecv.Broadcast()
return nil
}
// Iter returns an iterator that consumes values until the queue is closed.
func (s *state[T]) Iter() iter.Seq[T] {
return func(yield func(T) bool) {
for {
t, ok := s.Recv()
if !ok {
return
}
if !yield(t) {
return
}
}
}
}

View File

@ -164,6 +164,30 @@ func TestConcProducerConsumer(t *testing.T) {
wg.Wait()
}
func TestIter(t *testing.T) {
t.Parallel()
q := mq.Make[int](4)
q.Send(1)
q.Send(2)
q.Send(3)
q.SendHigh(0)
q.Close()
i := 0
for v := range q.Iter() {
if v != i {
t.Errorf("expected %d, got %d", i, v)
}
i++
}
// to test yield() returning false
q = mq.Make[int](4)
q.Send(3)
for _ = range q.Iter() {
break
}
}
func BenchmarkSend(b *testing.B) {
q := mq.Make[int](b.N)
b.ResetTimer()

View File

@ -38,6 +38,7 @@ package npq
import (
"fmt"
"iter"
"sync"
"gogs.humancabbage.net/sam/priorityq"
@ -212,3 +213,18 @@ func (s *state[P, T]) validatePriority(priority P) {
priority, s.min, s.max))
}
}
// Iter returns an iterator that consumes values until the queue is closed.
func (s *state[P, T]) Iter() iter.Seq[T] {
return func(yield func(T) bool) {
for {
t, ok := s.Recv()
if !ok {
return
}
if !yield(t) {
return
}
}
}
}

View File

@ -186,6 +186,30 @@ func TestConcProducerConsumer(t *testing.T) {
wg.Wait()
}
func TestIter(t *testing.T) {
t.Parallel()
q := npq.Make[int, int](4, 0, 16)
q.Send(4, 0)
q.Send(3, 1)
q.Send(2, 2)
q.Send(1, 3)
q.Close()
i := 0
for v := range q.Iter() {
if v != i {
t.Errorf("expected %d, got %d", i, v)
}
i++
}
// to test yield() returning false
q = npq.Make[int, int](4, 0, 16)
q.Send(1, 3)
for _ = range q.Iter() {
break
}
}
const highPriority = 2
func BenchmarkSend(b *testing.B) {

View File

@ -24,6 +24,7 @@
package pq
import (
"iter"
"sync"
"gogs.humancabbage.net/sam/priorityq"
@ -153,3 +154,18 @@ func (s *state[P, T]) TrySend(priority P, value T) error {
s.canRecv.Broadcast()
return nil
}
// Iter returns an iterator that consumes values until the queue is closed.
func (s *state[P, T]) Iter() iter.Seq[T] {
return func(yield func(T) bool) {
for {
_, t, ok := s.Recv()
if !ok {
return
}
if !yield(t) {
return
}
}
}
}

View File

@ -147,6 +147,30 @@ func TestConcProducerConsumer(t *testing.T) {
wg.Wait()
}
func TestIter(t *testing.T) {
t.Parallel()
q := pq.Make[int, int](4)
q.Send(4, 0)
q.Send(3, 1)
q.Send(2, 2)
q.Send(1, 3)
q.Close()
i := 0
for v := range q.Iter() {
if v != i {
t.Errorf("expected %d, got %d", i, v)
}
i++
}
// to test yield() returning false
q = pq.Make[int, int](4)
q.Send(1, 3)
for _ = range q.Iter() {
break
}
}
func BenchmarkSend(b *testing.B) {
q := pq.Make[int, int](b.N)
// randomize priorities to get amortized cost per op

View File

@ -6,6 +6,8 @@
// makes determining whether the queue is full or empty trivial.
package queue
import "iter"
// Q is a non-concurrent queue.
type Q[T any] struct {
buf []T
@ -73,3 +75,32 @@ func (b *Q[T]) PushBack(value T) {
b.tail = 0
}
}
// Iter returns an iterator over all items in the queue.
//
// This does not pop items off the queue.
func (b *Q[T]) Iter() iter.Seq[T] {
return func(yield func(T) bool) {
remaining := b.len
i := b.head
for remaining > 0 {
yield(b.buf[i])
remaining--
i++
if i == b.len {
i = 0
}
}
}
}
// IterPop returns an iterator that pops each item off the queue.
func (b *Q[T]) IterPop() iter.Seq[T] {
return func(yield func(T) bool) {
for b.CanPop() {
if !yield(b.PopFront()) {
break
}
}
}
}

View File

@ -73,6 +73,51 @@ func TestFullPushPanic(t *testing.T) {
q.PushBack(2)
}
func TestIter(t *testing.T) {
q := queue.Make[int](4)
q.PushBack(1)
q.PushBack(2)
q.PushBack(3)
q.PushBack(4)
i := 0
for v := range q.Iter() {
expected := i + 1
if v != expected {
t.Errorf("iter %d should have value %d, not %d",
i, expected, v)
}
i++
}
if q.Len() != 4 {
t.Errorf("wrong length")
}
}
func TestIterPop(t *testing.T) {
t.Parallel()
q := queue.Make[int](4)
q.PushBack(1)
q.PushBack(2)
q.PushBack(3)
q.PushBack(4)
i := 0
for v := range q.IterPop() {
expected := i + 1
if v != expected {
t.Errorf("iter %d should have value %d, not %d",
i, expected, v)
}
i++
// to test yield() returning false
if i == 4 {
break
}
}
if q.Len() != 0 {
t.Errorf("wrong length")
}
}
func BenchmarkPush(b *testing.B) {
q := queue.Make[int](b.N)
rs := rand.NewSource(0)