Skip to content
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
35 changes: 35 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: CI

on:
workflow_dispatch:
push:
branches:
- master
pull_request:

jobs:
go-vet:
name: go vet
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Setup Go
uses: actions/setup-go@v7
with:
go-version-file: go.mod
- name: go vet
run: go vet -v ./...

go-test:
name: go test
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Setup Go
uses: actions/setup-go@v7
with:
go-version-file: go.mod
- name: go test
run: go test -v ./...
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ xiter

[![Go Reference](https://pkg.go.dev/badge/deedles.dev/xiter.svg)](https://pkg.go.dev/deedles.dev/xiter)

xiter is a simple implementation of iterator utility functions for Go 1.23's iterators.
xiter is a simple implementation of Go iterator utility functionality.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module deedles.dev/xiter

go 1.24.6
go 1.27.0
102 changes: 50 additions & 52 deletions sink.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,22 @@ import (
"strings"
)

// Collect collects all items yielded by seq into a slice. It is
// equivalent to [slices.Collect] and is provided purely for
// convenience.
func (seq Seq[T]) Collect() []T {
return slices.Collect(seq.Seq())
}

// CollectSize pre-allocates the slice being collected into to the
// given size. It is provided purely for convenience.
func CollectSize[T any](seq iter.Seq[T], len int) []T {
return slices.AppendSeq(make([]T, 0, len), seq)
func (seq Seq[T]) CollectSize(len int) []T {
return slices.AppendSeq(make([]T, 0, len), seq.Seq())
}

// Find returns the first value of seq for which f(value) returns
// true.
func Find[T any](seq iter.Seq[T], f func(T) bool) (r T, ok bool) {
func (seq Seq[T]) Find(f func(T) bool) (r T, ok bool) {
seq(func(v T) bool {
if !f(v) {
return true
Expand All @@ -29,20 +36,20 @@ func Find[T any](seq iter.Seq[T], f func(T) bool) (r T, ok bool) {
}

// Contains returns true if v is an element of seq.
func Contains[T comparable](seq iter.Seq[T], v T) bool {
_, ok := Find(seq, func(e T) bool { return v == e })
func Contains[T comparable](seq Seq[T], v T) bool {
_, ok := seq.Find(func(e T) bool { return v == e })
return ok
}

// Any returns true if f(element) is true for any elements of seq.
func Any[T any](seq iter.Seq[T], f func(T) bool) bool {
_, ok := Find(seq, f)
func (seq Seq[T]) Any(f func(T) bool) bool {
_, ok := seq.Find(f)
return ok
}

// All returns true if f(element) is true for every element of seq.
func All[T any](seq iter.Seq[T], f func(T) bool) bool {
return !Any(seq, f)
func (seq Seq[T]) All(f func(T) bool) bool {
return !seq.Any(func(v T) bool { return !f(v) })
}

// Reduce calls reducer on each value of seq, passing it initial as
Expand All @@ -53,19 +60,19 @@ func All[T any](seq iter.Seq[T], f func(T) bool) bool {
// Reduce can be somewhat complicated to get the hang of, but very
// powerful. For example, a simple summation of values can be done as
//
// sum := Reduce(seq, 0, func(total, v int) int { return total + v })
func Reduce[T, R any](seq iter.Seq[T], initial R, reducer func(R, T) R) R {
// sum := seq.Reduce(0, func(total, v int) int { return total + v })
func (seq Seq[T]) Reduce[R any](initial R, reducer func(R, T) R) R {
seq(func(v T) bool {
initial = reducer(initial, v)
return true
})
return initial
}

// Fold performs a [Reduce] but uses the first value yielded by seq
// instead of a provided initial value. If seq doesn't yield any
// Fold performs a [Seq.Reduce] but uses the first value yielded by
// seq instead of a provided initial value. If seq doesn't yield any
// values, the zero value of T is returned.
func Fold[T any](seq iter.Seq[T], reducer func(T, T) T) T {
func (seq Seq[T]) Fold(reducer func(T, T) T) T {
var prev T
r := func(v1, v2 T) T { return v2 }
seq(func(v T) bool {
Expand All @@ -78,25 +85,25 @@ func Fold[T any](seq iter.Seq[T], reducer func(T, T) T) T {

// Sum returns the values of seq added together in the order that they
// are yielded.
func Sum[T Addable](seq iter.Seq[T]) T {
return Fold(seq, func(total, v T) T { return total + v })
func Sum[T Addable](seq Seq[T]) T {
return seq.Fold(func(total, v T) T { return total + v })
}

// Product returns the values of seq multiplied together. It returns
// 1 if no values are yielded.
func Product[T Multiplyable](seq iter.Seq[T]) T {
return Reduce(seq, 1, func(product, v T) T { return product * v })
func Product[T Multiplyable](seq Seq[T]) T {
return seq.Reduce(1, func(product, v T) T { return product * v })
}

// IsSorted returns true if each element of seq is greater than or
// equal to the previous one.
func IsSorted[T cmp.Ordered](seq iter.Seq[T]) bool {
return IsSortedFunc(seq, cmp.Compare)
func IsSorted[T cmp.Ordered](seq Seq[T]) bool {
return seq.IsSortedFunc(cmp.Compare)
}

// IsSortedFunc is like [IsSorted] but uses a custom comparison
// function.
func IsSortedFunc[T any](seq iter.Seq[T], compare func(T, T) int) bool {
func (seq Seq[T]) IsSortedFunc(compare func(T, T) int) bool {
var prev T
c := func(T, T) int { return -1 }

Expand All @@ -112,16 +119,16 @@ func IsSortedFunc[T any](seq iter.Seq[T], compare func(T, T) int) bool {
// Equal returns true if seq1 and seq2 are the same length and each
// element of each is equal to the element at the same point in the
// sequence of the other.
func Equal[T cmp.Ordered](seq1, seq2 iter.Seq[T]) bool {
func Equal[T cmp.Ordered](seq1, seq2 Seq[T]) bool {
return EqualFunc(seq1, seq2, func(v1, v2 T) bool { return v1 == v2 })
}

// EqualFunc is like [Equal] but uses a custom comparison function to
// determine the equivalence of the elements of each sequence.
func EqualFunc[T1, T2 any](seq1 iter.Seq[T1], seq2 iter.Seq[T2], equal func(T1, T2) bool) bool {
p1, stop := iter.Pull(seq1)
func EqualFunc[T1, T2 any](seq1 Seq[T1], seq2 Seq[T2], equal func(T1, T2) bool) bool {
p1, stop := iter.Pull(seq1.Seq())
defer stop()
p2, stop := iter.Pull(seq2)
p2, stop := iter.Pull(seq2.Seq())
defer stop()

for {
Expand All @@ -138,7 +145,7 @@ func EqualFunc[T1, T2 any](seq1 iter.Seq[T1], seq2 iter.Seq[T2], equal func(T1,

// Drain empties seq, returning the last value yielded. If no values
// are yielded, ok will be false.
func Drain[T any](seq iter.Seq[T]) (v T, ok bool) {
func (seq Seq[T]) Drain() (v T, ok bool) {
seq(func(val T) bool {
v = val
ok = true
Expand All @@ -148,13 +155,13 @@ func Drain[T any](seq iter.Seq[T]) (v T, ok bool) {
}

// CollectSplit is like [Collect], but for a SplitSeq.
func CollectSplit[T1, T2 any](seq SplitSeq[T1, T2]) (y1 []T1, y2 []T2) {
return AppendSplitTo(seq, y1, y2)
func (seq SplitSeq[T1, T2]) CollectSplit() (y1 []T1, y2 []T2) {
return seq.AppendSplitTo(y1, y2)
}

// AppendSplitTo collects the elements of seq by appending them to
// existing slices.
func AppendSplitTo[T1, T2 any](seq SplitSeq[T1, T2], s1 []T1, s2 []T2) ([]T1, []T2) {
func (seq SplitSeq[T1, T2]) AppendSplitTo(s1 []T1, s2 []T2) ([]T1, []T2) {
seq(
func(v T1) bool {
s1 = append(s1, v)
Expand All @@ -171,41 +178,32 @@ func AppendSplitTo[T1, T2 any](seq SplitSeq[T1, T2], s1 []T1, s2 []T2) ([]T1, []
// Partition returns two slices, one containing all of the elements of
// seq for which f(element) is true and one containing all of those
// for which it is false.
func Partition[T any](seq iter.Seq[T], f func(T) bool) (true, false []T) {
return PartitionInto(seq, f, true, false)
func (seq Seq[T]) Partition(f func(T) bool) (true, false []T) {
return seq.PartitionInto(f, true, false)
}

// PartitionInto performs a [Partition] by appending to two existing
// slices.
func PartitionInto[T any](seq iter.Seq[T], f func(T) bool, true, false []T) ([]T, []T) {
return AppendSplitTo(Split(seq, f), true, false)
// PartitionInto performs a [Seq.Partition] by appending to two
// existing slices.
func (seq Seq[T]) PartitionInto(f func(T) bool, true, false []T) ([]T, []T) {
return seq.Split(f).AppendSplitTo(true, false)
}

// Min returns the minimum element yielded by seq or the zero value if
// seq doesn't yield anything.
func Min[T cmp.Ordered](seq iter.Seq[T]) T {
return Fold(seq, func(v1, v2 T) T { return min(v1, v2) })
func Min[T cmp.Ordered](seq Seq[T]) T {
return seq.Fold(func(v1, v2 T) T { return min(v1, v2) })
}

// Max returns maximum element yielded by seq or the zero value if seq
// doesn't yield anything.
func Max[T cmp.Ordered](seq iter.Seq[T]) T {
return Fold(seq, func(v1, v2 T) T { return max(v1, v2) })
}

// FromPair converts a Seq of pairs to a two-value Seq.
func FromPair[T1, T2 any](seq iter.Seq[Pair[T1, T2]]) iter.Seq2[T1, T2] {
return func(yield func(T1, T2) bool) {
seq(func(v Pair[T1, T2]) bool {
return yield(v.Split())
})
}
func Max[T cmp.Ordered](seq Seq[T]) T {
return seq.Fold(func(v1, v2 T) T { return max(v1, v2) })
}

// SendContext sends values from seq to c repeatedly until either the
// sequence ends or ctx is canceled. It blocks until one of those two
// things happens.
func SendContext[T any](seq iter.Seq[T], ctx context.Context, c chan<- T) {
func (seq Seq[T]) SendContext(ctx context.Context, c chan<- T) {
seq(func(v T) bool {
select {
case <-ctx.Done():
Expand All @@ -216,9 +214,9 @@ func SendContext[T any](seq iter.Seq[T], ctx context.Context, c chan<- T) {
})
}

// StringJoin works exactly like [strings.Join] but it operates on an
// [iter.Seq] instead of a []string.
func StringJoin(seq iter.Seq[string], sep string) string {
// StringJoin works exactly like [strings.Join] but it operates on a
// [Seq] instead of a []string.
func StringJoin(seq Seq[string], sep string) string {
var buf strings.Builder
var s string
for v := range seq {
Expand Down
44 changes: 29 additions & 15 deletions sink_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,7 @@ import (
)

func TestFind(t *testing.T) {
s, _ := Find(Windows(Generate(
0, 1),
3),
func(win []int) bool { return Sum(slices.Values(win)) >= 100 })
s, _ := Windows(Generate(0, 1), 3).Find(func(win []int) bool { return Sum(Of(win...)) >= 100 })
if [3]int(s) != [...]int{33, 34, 35} {
t.Fatal(s)
}
Expand All @@ -24,7 +21,7 @@ func TestContains(t *testing.T) {
}

func TestSum(t *testing.T) {
s := Sum(slices.Values([]string{"a", " ", "test"}))
s := Sum(Of("a", " ", "test"))
if s != "a test" {
t.Fatal(s)
}
Expand All @@ -38,11 +35,11 @@ func TestProduct(t *testing.T) {
}

func TestPartition(t *testing.T) {
s1, s2 := Partition(Of(1, 2, 3, 4, 5), func(v int) bool { return v%2 == 0 })
if !Equal(slices.Values(s1), Of(2, 4)) {
s1, s2 := Of(1, 2, 3, 4, 5).Partition(func(v int) bool { return v%2 == 0 })
if !Equal(Of(s1...), Of(2, 4)) {
t.Fatal(s1)
}
if !Equal(slices.Values(s2), Of(1, 3, 5)) {
if !Equal(Of(s2...), Of(1, 3, 5)) {
t.Fatal(s2)
}
}
Expand All @@ -60,22 +57,39 @@ func TestExtent(t *testing.T) {
}

func TestAny(t *testing.T) {
r := Any(Of(2, 4, 6, 7), func(v int) bool { return v%2 != 0 })
r := Of(2, 4, 6, 7).Any(func(v int) bool { return v%2 != 0 })
if !r {
t.Fatal(r)
}
}

func TestAll(t *testing.T) {
r := All(Of(2, 4, 6, 7), func(v int) bool { return v%2 == 0 })
even := func(v int) bool { return v%2 == 0 }

r := Of(2, 4, 6).All(even)
if !r {
t.Fatal(r)
}

r = Of(2, 4, 6, 7).All(even)
if r {
t.Fatal(r)
}

r = Of(1, 3, 5).All(even)
if r {
t.Fatal(r)
}

r = Of[int]().All(even)
if !r {
t.Fatal(r)
}
}

func TestSendContext(t *testing.T) {
c := make(chan int, 3)
SendContext(Of(3, 2, 5), context.Background(), c)
Of(3, 2, 5).SendContext(context.Background(), c)
s := []int{<-c, <-c, <-c}
select {
case v := <-c:
Expand All @@ -95,22 +109,22 @@ func FuzzSendRecvContext(f *testing.F) {
defer cancel()

c := make(chan byte, len(data))
SendContext(slices.Values(data), ctx, c)
S(slices.Values(data)).SendContext(ctx, c)
close(c)
s := slices.Collect(RecvContext(ctx, c))
s := RecvContext(ctx, c).Collect()
if !slices.Equal(data, s) {
t.Fatal(s)
}
})
}

func TestDrain(t *testing.T) {
v, ok := Drain(Of(3, 2, 5))
v, ok := Of(3, 2, 5).Drain()
if !ok || v != 5 {
t.Fatalf("%v, %v", v, ok)
}

v, ok = Drain(Of[int]())
v, ok = Of[int]().Drain()
if ok || v != 0 {
t.Fatalf("%v, %v", v, ok)
}
Expand Down
Loading