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

all: change the signature of Each to be a range function #20

Merged
merged 1 commit into from
Sep 11, 2024
Merged
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
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