From 8bc6ae13899c39238acf9462f1f511064f5ba31d Mon Sep 17 00:00:00 2001 From: "M. J. Fromberger" Date: Wed, 11 Sep 2024 15:48:19 -0700 Subject: [PATCH] all: change the signature of Each to be a range function Most of the collections defined here already have an Each method that iterates the contents of the collection and was almost compatible with the new range function interface added in Go 1.23. Here, we make it fully compatible, by discarding the Boolean return value from the Each method itself. This bumps the required Go version from 1.22 to 1.23. Technically we could avoid that by not updating the usage within the package itself, but as far as I am aware all the consumers of this package are using Go 1.23 already. --- README.md | 10 ++++++++++ go.mod | 2 +- heapq/heapq.go | 10 ++++------ heapq/heapq_test.go | 5 ++--- internal/mdtest/mdtest.go | 8 +++++--- mlink/example_test.go | 5 ++--- mlink/list.go | 19 +++++++++---------- mlink/mlink.go | 8 -------- mlink/queue.go | 7 +++---- queue/queue.go | 10 ++++------ ring/example_test.go | 5 ++--- ring/ring.go | 16 +++++++--------- stack/stack.go | 10 ++++------ 13 files changed, 53 insertions(+), 62 deletions(-) diff --git a/README.md b/README.md index c237f43..4f3defd 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,16 @@ This repository defines generic data structures in Go. ## Data Structures +Most of the types in this module share common behaviors: + +- A `Clear` method that discards all the contents of the container. +- A `Peek` method that returns an order statistic of the container. +- An `Each` method that iterates the container in its natural order (usable as a [range function](https://go.dev/blog/range-functions)). +- An `IsEmpty` method that reports whether the container is empty. +- A `Len` method that reports the number of elements in the container. + +### Packages + - [heapq](./heapq) a heap-structured priority queue ([package docs](https://godoc.org/github.com/creachadair/mds/heapq)) - [mapset](./mapset) a basic map-based set implementation ([package docs](https://godoc.org/github.com/creachadair/mds/mapset)) - [mlink](./mlink) basic linked sequences (list, queue) ([package docs](https://godoc.org/github.com/creachadair/mds/mlink)) diff --git a/go.mod b/go.mod index 7ce2541..49ef26e 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,5 @@ module github.com/creachadair/mds -go 1.22 +go 1.23 require github.com/google/go-cmp v0.6.0 diff --git a/heapq/heapq.go b/heapq/heapq.go index fb65818..ce8ac03 100644 --- a/heapq/heapq.go +++ b/heapq/heapq.go @@ -156,16 +156,14 @@ func (q *Queue[T]) Reorder(cmp func(a, b T) int) { } } -// Each calls f for each value in q in heap order. If f returns false, Each -// stops and returns false. Otherwise, Each returns true after visiting all -// elements of q. -func (q *Queue[T]) Each(f func(T) bool) bool { +// Each is a range function that calls f with each value in q in heap order. +// If f returns false, Each returns immediately. +func (q *Queue[T]) Each(f func(T) bool) { for _, v := range q.data { if !f(v) { - return false + return } } - return true } // Clear discards all the entries in q, leaving it empty. diff --git a/heapq/heapq_test.go b/heapq/heapq_test.go index 21da2dc..a4a7fb1 100644 --- a/heapq/heapq_test.go +++ b/heapq/heapq_test.go @@ -32,10 +32,9 @@ func runTests(t *testing.T, q *heapq.Queue[int]) { check := func(want ...int) { t.Helper() var got []int - q.Each(func(v int) bool { + for v := range q.Each { got = append(got, v) - return true - }) + } if diff := cmp.Diff(want, got); diff != "" { t.Errorf("Queue contents (+want, -got):\n%s", diff) t.Logf("Got: %v", got) diff --git a/internal/mdtest/mdtest.go b/internal/mdtest/mdtest.go index c146054..ed7b9a2 100644 --- a/internal/mdtest/mdtest.go +++ b/internal/mdtest/mdtest.go @@ -13,14 +13,14 @@ import ( type Shared[T any] interface { Clear() Peek(int) (T, bool) - Each(func(T) bool) bool + Each(func(T) bool) IsEmpty() bool Len() int } // Eacher is the subset of Shared provided by iterable elements. type Eacher[T any] interface { - Each(func(T) bool) bool + Each(func(T) bool) Len() int } @@ -29,7 +29,9 @@ type Eacher[T any] interface { func CheckContents[T any](t *testing.T, s Eacher[T], want []T) { t.Helper() var got []T - s.Each(func(v T) bool { got = append(got, v); return true }) + for v := range s.Each { + got = append(got, v) + } if diff := cmp.Diff(want, got, cmpopts.EquateEmpty()); diff != "" { t.Errorf("Wrong contents (-got, +want):\n%s", diff) } diff --git a/mlink/example_test.go b/mlink/example_test.go index aa02808..acd7208 100644 --- a/mlink/example_test.go +++ b/mlink/example_test.go @@ -31,10 +31,9 @@ func ExampleList() { name.Remove() // Print out everything in the list. - lst.Each(func(s string) bool { + for s := range lst.Each { fmt.Print(" ", s) - return true - }) + } fmt.Println() // Calculate the length of the list. diff --git a/mlink/list.go b/mlink/list.go index 11ded93..caddf20 100644 --- a/mlink/list.go +++ b/mlink/list.go @@ -29,24 +29,23 @@ func (lst *List[T]) Peek(n int) (T, bool) { return cur.Get(), !cur.AtEnd() } -// Each calls f with each value in lst, in order from first to last. -// If f returns false, Each stops and returns false. -// Otherwise, Each returns true after visiting all elements of lst. -func (lst *List[T]) Each(f func(T) bool) bool { +// Each is a range function that calls f with each value in lst in order from +// first to last. If f returns false, Each returns immediately. +func (lst *List[T]) Each(f func(T) bool) { for cur := lst.cfirst(); !cur.AtEnd(); cur.Next() { if !f(cur.Get()) { - return false + return } } - return true } // Len reports the number of elements in lst. This method takes time proportional // to the length of the list. -func (lst *List[T]) Len() int { - var n int - lst.Each(func(T) bool { n++; return true }) - return n +func (lst *List[T]) Len() (n int) { + for range lst.Each { + n++ + } + return } // At returns a cursor to the element at index n ≥ 0 in the list. If n is diff --git a/mlink/mlink.go b/mlink/mlink.go index ce7704e..8ae776c 100644 --- a/mlink/mlink.go +++ b/mlink/mlink.go @@ -1,13 +1,5 @@ // Package mlink implements basic linked container data structures. // -// Most types in this package share certain common behaviors: -// -// - A Clear method that discards all the contents of the container. -// - A Peek method that returns an order statistic of the container. -// - An Each method that iterates the container in its natural order. -// - An IsEmpty method that reports whether the container is empty. -// - A Len method that reports the number of elements in the container. -// // The types defined here are not safe for concurrent use by multiple // goroutines without external synchronization. package mlink diff --git a/mlink/queue.go b/mlink/queue.go index 5809856..1e16783 100644 --- a/mlink/queue.go +++ b/mlink/queue.go @@ -56,10 +56,9 @@ func (q *Queue[T]) Pop() (T, bool) { return out, true } -// Each calls f with each value in q, in order from oldest to newest. -// If f returns false, Each stops and returns false. -// Otherwise, Each returns true after visiting all elements of q. -func (q *Queue[T]) Each(f func(T) bool) bool { return q.list.Each(f) } +// Each is a range function that calls f with each value in q, in order from +// oldest to newest. If f returns false, Each returns immediately. +func (q *Queue[T]) Each(f func(T) bool) { q.list.Each(f) } // Len reports the number of elements in q. This is a constant-time operation. func (q *Queue[T]) Len() int { return q.size } diff --git a/queue/queue.go b/queue/queue.go index 3de6032..527989a 100644 --- a/queue/queue.go +++ b/queue/queue.go @@ -148,18 +148,16 @@ func (q *Queue[T]) PopLast() (T, bool) { return out, true } -// Each calls f with each value in q, in order from oldest to newest. -// If f returns false, Each stops and returns false. -// Otherwise, Each returns true after visiting all elements of q. -func (q *Queue[T]) Each(f func(T) bool) bool { +// Each is a range function that calls f with each value, in q, in order from +// oldest to newest. If f returns false, Each returns immediately. +func (q *Queue[T]) Each(f func(T) bool) { cur := q.head for range q.n { if !f(q.vs[cur]) { - return false + return } cur = (cur + 1) % len(q.vs) } - return true } // Slice returns a slice of the values of q in order from oldest to newest. diff --git a/ring/example_test.go b/ring/example_test.go index 0b2224f..38f7dd2 100644 --- a/ring/example_test.go +++ b/ring/example_test.go @@ -19,10 +19,9 @@ func ExampleRing() { s.Prev().Join(r) // Iterate over the elements of a ring. - r.Each(func(s string) bool { + for s := range r.Each { fmt.Println(s) - return true - }) + } // Output: // fruit diff --git a/ring/ring.go b/ring/ring.go index cd14c67..ab7805d 100644 --- a/ring/ring.go +++ b/ring/ring.go @@ -150,11 +150,10 @@ func (r *Ring[T]) Peek(n int) (T, bool) { return cur.Value, true } -// Each calls f with each value in r, in circular order. If f returns false, -// Each stops and returns false. Otherwise, Each returns true after visiting -// all elements of r. -func (r *Ring[T]) Each(f func(v T) bool) bool { - return scan(r, func(cur *Ring[T]) bool { return f(cur.Value) }) +// Each is a range function that calls f with each value of r in circular +// order. If f returns false, Each returns immediately. +func (r *Ring[T]) Each(f func(v T) bool) { + scan(r, func(cur *Ring[T]) bool { return f(cur.Value) }) } // Len reports the number of elements in r. If r == nil, Len is 0. @@ -171,19 +170,18 @@ func (r *Ring[T]) Len() int { // IsEmpty reports whether r is the empty ring. func (r *Ring[T]) IsEmpty() bool { return r == nil } -func scan[T any](r *Ring[T], f func(*Ring[T]) bool) bool { +func scan[T any](r *Ring[T], f func(*Ring[T]) bool) { if r == nil { - return true + return } cur := r for f(cur) { if cur.next == r { - return true + return } cur = cur.next } - return false } func newRing[T any]() *Ring[T] { r := new(Ring[T]); r.next = r; r.prev = r; return r } diff --git a/stack/stack.go b/stack/stack.go index f574904..6430288 100644 --- a/stack/stack.go +++ b/stack/stack.go @@ -56,16 +56,14 @@ func (s *Stack[T]) Pop() (T, bool) { return out, ok } -// Each calls f with each value in s, in order from newest to oldest. -// If f returns false, Each stops and returns false. -// Otherwise, Each returns true after visiting all elements of s. -func (s *Stack[T]) Each(f func(T) bool) bool { +// Each is a range function that calls f with each value in s, in order from +// newest to oldest. If f returns false, Each returns immediately. +func (s *Stack[T]) Each(f func(T) bool) { for i := len(s.list) - 1; i >= 0; i-- { if !f(s.list[i]) { - return false + return } } - return true } // Len reports the number of elements in s. This is a constant-time operation.