Skip to content

Commit

Permalink
all: change the signature of Each to be a range function (#20)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
creachadair authored Sep 11, 2024
1 parent 2f82efd commit 533a344
Show file tree
Hide file tree
Showing 13 changed files with 53 additions and 62 deletions.
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module github.com/creachadair/mds

go 1.22
go 1.23

require github.com/google/go-cmp v0.6.0
10 changes: 4 additions & 6 deletions heapq/heapq.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
5 changes: 2 additions & 3 deletions heapq/heapq_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
8 changes: 5 additions & 3 deletions internal/mdtest/mdtest.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand All @@ -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)
}
Expand Down
5 changes: 2 additions & 3 deletions mlink/example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
19 changes: 9 additions & 10 deletions mlink/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 0 additions & 8 deletions mlink/mlink.go
Original file line number Diff line number Diff line change
@@ -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
Expand Down
7 changes: 3 additions & 4 deletions mlink/queue.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
10 changes: 4 additions & 6 deletions queue/queue.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
5 changes: 2 additions & 3 deletions ring/example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
16 changes: 7 additions & 9 deletions ring/ring.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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 }
10 changes: 4 additions & 6 deletions stack/stack.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down

0 comments on commit 533a344

Please sign in to comment.