diff --git a/README.md b/README.md index 825a420..c0c1cc8 100644 --- a/README.md +++ b/README.md @@ -4,10 +4,10 @@ A curated collection of utility functions and types absent from the Go standard library that make life easier. -See documentation: https://pkg.go.dev/github.com/jpfourny/papaya +See documentation: https://pkg.go.dev/github.com/jpfourny/papaya/v2 See tests and examples for usage. ## Installation -`go get github.com/jpfourny/papaya` +`go get github.com/jpfourny/papaya/v2` diff --git a/examples/people.go b/examples/people.go index 95eb756..9e242cd 100644 --- a/examples/people.go +++ b/examples/people.go @@ -3,7 +3,7 @@ package examples import ( "fmt" - "github.com/jpfourny/papaya/pkg/cmp" + "github.com/jpfourny/papaya/v2/pkg/cmp" ) type Person struct { diff --git a/examples/stream/map_test.go b/examples/stream/map_test.go index f11633f..39d36d4 100644 --- a/examples/stream/map_test.go +++ b/examples/stream/map_test.go @@ -2,11 +2,11 @@ package stream import ( "fmt" - "github.com/jpfourny/papaya/pkg/stream/mapper" + "github.com/jpfourny/papaya/v2/pkg/stream/mapper" "testing" - "github.com/jpfourny/papaya/pkg/pair" - "github.com/jpfourny/papaya/pkg/stream" + "github.com/jpfourny/papaya/v2/pkg/pair" + "github.com/jpfourny/papaya/v2/pkg/stream" ) func TestMapIntToString(t *testing.T) { diff --git a/examples/stream/sort_test.go b/examples/stream/sort_test.go index 709690a..e002057 100644 --- a/examples/stream/sort_test.go +++ b/examples/stream/sort_test.go @@ -4,9 +4,9 @@ import ( "fmt" "testing" - "github.com/jpfourny/papaya/examples" - "github.com/jpfourny/papaya/pkg/cmp" - "github.com/jpfourny/papaya/pkg/stream" + "github.com/jpfourny/papaya/v2/examples" + "github.com/jpfourny/papaya/v2/pkg/cmp" + "github.com/jpfourny/papaya/v2/pkg/stream" ) func TestSort(t *testing.T) { diff --git a/go.mod b/go.mod index ed2c543..7a8814d 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ -module github.com/jpfourny/papaya +module github.com/jpfourny/papaya/v2 go 1.23 diff --git a/internal/kvstore/kvstore.go b/internal/kvstore/kvstore.go index 18bf085..0053809 100644 --- a/internal/kvstore/kvstore.go +++ b/internal/kvstore/kvstore.go @@ -1,8 +1,8 @@ package kvstore import ( - "github.com/jpfourny/papaya/pkg/cmp" - "github.com/jpfourny/papaya/pkg/opt" + "github.com/jpfourny/papaya/v2/pkg/cmp" + "github.com/jpfourny/papaya/v2/pkg/opt" "slices" ) diff --git a/internal/kvstore/kvstore_test.go b/internal/kvstore/kvstore_test.go index 32f8001..ae16857 100644 --- a/internal/kvstore/kvstore_test.go +++ b/internal/kvstore/kvstore_test.go @@ -1,9 +1,9 @@ package kvstore import ( - "github.com/jpfourny/papaya/internal/assert" - "github.com/jpfourny/papaya/pkg/cmp" - "github.com/jpfourny/papaya/pkg/opt" + "github.com/jpfourny/papaya/v2/internal/assert" + "github.com/jpfourny/papaya/v2/pkg/cmp" + "github.com/jpfourny/papaya/v2/pkg/opt" "testing" ) diff --git a/pkg/cmp/comparer.go b/pkg/cmp/comparer.go index 4886811..6d3591b 100644 --- a/pkg/cmp/comparer.go +++ b/pkg/cmp/comparer.go @@ -4,8 +4,8 @@ import ( stdcmp "cmp" "time" - "github.com/jpfourny/papaya/pkg/constraint" - "github.com/jpfourny/papaya/pkg/pair" + "github.com/jpfourny/papaya/v2/pkg/constraint" + "github.com/jpfourny/papaya/v2/pkg/pair" ) // Comparer is a function that compares two values of the same type E and returns an integer. diff --git a/pkg/cmp/comparer_test.go b/pkg/cmp/comparer_test.go index 4b5ec9f..305866d 100644 --- a/pkg/cmp/comparer_test.go +++ b/pkg/cmp/comparer_test.go @@ -5,8 +5,8 @@ import ( "testing" "time" - "github.com/jpfourny/papaya/pkg/pair" - "github.com/jpfourny/papaya/pkg/ptr" + "github.com/jpfourny/papaya/v2/pkg/pair" + "github.com/jpfourny/papaya/v2/pkg/ptr" ) type Person struct { diff --git a/pkg/env/env.go b/pkg/env/env.go index a25ac5a..1d9e55f 100644 --- a/pkg/env/env.go +++ b/pkg/env/env.go @@ -1,11 +1,11 @@ package env import ( - "github.com/jpfourny/papaya/pkg/constraint" - "github.com/jpfourny/papaya/pkg/opt" - "github.com/jpfourny/papaya/pkg/pair" - "github.com/jpfourny/papaya/pkg/stream" - "github.com/jpfourny/papaya/pkg/stream/mapper" + "github.com/jpfourny/papaya/v2/pkg/constraint" + "github.com/jpfourny/papaya/v2/pkg/opt" + "github.com/jpfourny/papaya/v2/pkg/pair" + "github.com/jpfourny/papaya/v2/pkg/stream" + "github.com/jpfourny/papaya/v2/pkg/stream/mapper" "os" "strings" "time" diff --git a/pkg/env/env_test.go b/pkg/env/env_test.go index 82aed32..68d0369 100644 --- a/pkg/env/env_test.go +++ b/pkg/env/env_test.go @@ -1,8 +1,8 @@ package env import ( - "github.com/jpfourny/papaya/pkg/pair" - "github.com/jpfourny/papaya/pkg/stream" + "github.com/jpfourny/papaya/v2/pkg/pair" + "github.com/jpfourny/papaya/v2/pkg/stream" "os" "testing" "time" diff --git a/pkg/ptr/pointer.go b/pkg/ptr/pointer.go index 1f3823f..b17001f 100644 --- a/pkg/ptr/pointer.go +++ b/pkg/ptr/pointer.go @@ -1,6 +1,6 @@ package ptr -import "github.com/jpfourny/papaya/pkg/opt" +import "github.com/jpfourny/papaya/v2/pkg/opt" // Nil returns a nil pointer to the type parameter V. func Nil[V any]() *V { diff --git a/pkg/res/failure.go b/pkg/res/failure.go index b43cbac..77f9f8a 100644 --- a/pkg/res/failure.go +++ b/pkg/res/failure.go @@ -2,7 +2,7 @@ package res import ( "fmt" - "github.com/jpfourny/papaya/pkg/opt" + "github.com/jpfourny/papaya/v2/pkg/opt" ) // Failure represents a failed result. diff --git a/pkg/res/partial_success.go b/pkg/res/partial_success.go index 2500858..40d7a5f 100644 --- a/pkg/res/partial_success.go +++ b/pkg/res/partial_success.go @@ -2,7 +2,7 @@ package res import ( "fmt" - "github.com/jpfourny/papaya/pkg/opt" + "github.com/jpfourny/papaya/v2/pkg/opt" ) // PartialSuccess represents a partially-successful result. diff --git a/pkg/res/result.go b/pkg/res/result.go index 1ae5bf6..47cf492 100644 --- a/pkg/res/result.go +++ b/pkg/res/result.go @@ -2,7 +2,7 @@ package res import ( "fmt" - "github.com/jpfourny/papaya/pkg/opt" + "github.com/jpfourny/papaya/v2/pkg/opt" ) // Result represents the result of an operation that may have a value and/or an error. diff --git a/pkg/res/success.go b/pkg/res/success.go index 67edfcb..79f363f 100644 --- a/pkg/res/success.go +++ b/pkg/res/success.go @@ -2,7 +2,7 @@ package res import ( "fmt" - "github.com/jpfourny/papaya/pkg/opt" + "github.com/jpfourny/papaya/v2/pkg/opt" ) // Success represents a successful result. diff --git a/pkg/stream/aggregate.go b/pkg/stream/aggregate.go index 7059054..69b89a3 100644 --- a/pkg/stream/aggregate.go +++ b/pkg/stream/aggregate.go @@ -1,11 +1,11 @@ package stream import ( - "github.com/jpfourny/papaya/pkg/cmp" - "github.com/jpfourny/papaya/pkg/constraint" - "github.com/jpfourny/papaya/pkg/opt" - "github.com/jpfourny/papaya/pkg/stream/mapper" - "github.com/jpfourny/papaya/pkg/stream/reducer" + "github.com/jpfourny/papaya/v2/pkg/cmp" + "github.com/jpfourny/papaya/v2/pkg/constraint" + "github.com/jpfourny/papaya/v2/pkg/opt" + "github.com/jpfourny/papaya/v2/pkg/stream/mapper" + "github.com/jpfourny/papaya/v2/pkg/stream/reducer" ) // Reducer represents a function that takes two inputs of type E and returns an output of type E. diff --git a/pkg/stream/aggregate_test.go b/pkg/stream/aggregate_test.go index db67be0..db3c609 100644 --- a/pkg/stream/aggregate_test.go +++ b/pkg/stream/aggregate_test.go @@ -1,10 +1,10 @@ package stream import ( - "github.com/jpfourny/papaya/pkg/cmp" + "github.com/jpfourny/papaya/v2/pkg/cmp" "testing" - "github.com/jpfourny/papaya/pkg/opt" + "github.com/jpfourny/papaya/v2/pkg/opt" ) func TestReduce(t *testing.T) { diff --git a/pkg/stream/collect.go b/pkg/stream/collect.go index 24965d7..824c71e 100644 --- a/pkg/stream/collect.go +++ b/pkg/stream/collect.go @@ -2,9 +2,9 @@ package stream import ( "context" - "github.com/jpfourny/papaya/pkg/stream/mapper" + "github.com/jpfourny/papaya/v2/pkg/stream/mapper" - "github.com/jpfourny/papaya/pkg/pair" + "github.com/jpfourny/papaya/v2/pkg/pair" ) // CollectSlice returns a slice containing all elements from the stream. diff --git a/pkg/stream/collect_test.go b/pkg/stream/collect_test.go index 7146903..72e2633 100644 --- a/pkg/stream/collect_test.go +++ b/pkg/stream/collect_test.go @@ -4,8 +4,8 @@ import ( "context" "testing" - "github.com/jpfourny/papaya/internal/assert" - "github.com/jpfourny/papaya/pkg/pair" + "github.com/jpfourny/papaya/v2/internal/assert" + "github.com/jpfourny/papaya/v2/pkg/pair" ) func TestCollectSlice(t *testing.T) { diff --git a/pkg/stream/combine.go b/pkg/stream/combine.go index 3ed0138..5e7f563 100644 --- a/pkg/stream/combine.go +++ b/pkg/stream/combine.go @@ -2,11 +2,11 @@ package stream import ( "context" - "github.com/jpfourny/papaya/pkg/constraint" - "github.com/jpfourny/papaya/pkg/opt" - "github.com/jpfourny/papaya/pkg/pair" - "github.com/jpfourny/papaya/pkg/stream/mapper" - "github.com/jpfourny/papaya/pkg/stream/pred" + "github.com/jpfourny/papaya/v2/pkg/constraint" + "github.com/jpfourny/papaya/v2/pkg/opt" + "github.com/jpfourny/papaya/v2/pkg/pair" + "github.com/jpfourny/papaya/v2/pkg/stream/mapper" + "github.com/jpfourny/papaya/v2/pkg/stream/pred" ) // Combiner represents a function that combines two elements of type E1 and E2 into an element of type F. diff --git a/pkg/stream/combine_test.go b/pkg/stream/combine_test.go index f2d2f63..d20c784 100644 --- a/pkg/stream/combine_test.go +++ b/pkg/stream/combine_test.go @@ -4,9 +4,9 @@ import ( "fmt" "testing" - "github.com/jpfourny/papaya/internal/assert" - "github.com/jpfourny/papaya/pkg/opt" - "github.com/jpfourny/papaya/pkg/pair" + "github.com/jpfourny/papaya/v2/internal/assert" + "github.com/jpfourny/papaya/v2/pkg/opt" + "github.com/jpfourny/papaya/v2/pkg/pair" ) func TestCombine(t *testing.T) { diff --git a/pkg/stream/consumer.go b/pkg/stream/consumer.go new file mode 100644 index 0000000..c2d1171 --- /dev/null +++ b/pkg/stream/consumer.go @@ -0,0 +1,18 @@ +package stream + +// Consumer represents a function that accepts a yielded element of type E and returns a boolean value. +// The boolean value indicates whether the consumer wishes to continue accepting elements. +// If the consumer returns false, the caller must stop yielding elements. +type Consumer[E any] func(yield E) (cont bool) + +func stopSensingConsumer[E any](c Consumer[E]) (Consumer[E], *bool) { + stopped := new(bool) + c2 := func(e E) bool { + if c(e) { + return true + } + *stopped = true + return false + } + return c2, stopped +} diff --git a/pkg/stream/consumer_test.go b/pkg/stream/consumer_test.go new file mode 100644 index 0000000..407cced --- /dev/null +++ b/pkg/stream/consumer_test.go @@ -0,0 +1,44 @@ +package stream + +import ( + "slices" + "testing" +) + +func Test_stopSensingConsumer(t *testing.T) { + t.Run("stream-exhausted", func(t *testing.T) { + s := Of(1, 2, 3) + var saw []int + c1 := func(e int) bool { + saw = append(saw, e) + return true + } + c2, stopped := stopSensingConsumer(c1) + s(c2) + if *stopped { + t.Errorf("expected stopped to be false; got true") + } + want := []int{1, 2, 3} + if !slices.Equal(saw, want) { + t.Errorf("expected to see %v; got %v", want, saw) + } + }) + + t.Run("consumer-stopped", func(t *testing.T) { + s := Of(1, 2, 3) + var saw []int + c1 := func(e int) bool { + saw = append(saw, e) + return false + } + c2, stopped := stopSensingConsumer(c1) + s(c2) + if !*stopped { + t.Errorf("expected stopped to be true; got false") + } + want := []int{1} + if !slices.Equal(saw, want) { + t.Errorf("expected to see %v; got %v", want, saw) + } + }) +} diff --git a/pkg/stream/filter.go b/pkg/stream/filter.go index 79e5b9f..45b8594 100644 --- a/pkg/stream/filter.go +++ b/pkg/stream/filter.go @@ -1,9 +1,9 @@ package stream import ( - "github.com/jpfourny/papaya/internal/kvstore" - "github.com/jpfourny/papaya/pkg/cmp" - "github.com/jpfourny/papaya/pkg/pair" + "github.com/jpfourny/papaya/v2/internal/kvstore" + "github.com/jpfourny/papaya/v2/pkg/cmp" + "github.com/jpfourny/papaya/v2/pkg/pair" ) // Predicate is a function that accepts a value of type E and returns a boolean. diff --git a/pkg/stream/filter_test.go b/pkg/stream/filter_test.go index 6986e81..92066c0 100644 --- a/pkg/stream/filter_test.go +++ b/pkg/stream/filter_test.go @@ -1,11 +1,11 @@ package stream import ( - "github.com/jpfourny/papaya/pkg/cmp" - "github.com/jpfourny/papaya/pkg/pair" + "github.com/jpfourny/papaya/v2/pkg/cmp" + "github.com/jpfourny/papaya/v2/pkg/pair" "testing" - "github.com/jpfourny/papaya/internal/assert" + "github.com/jpfourny/papaya/v2/internal/assert" ) func TestFilter(t *testing.T) { diff --git a/pkg/stream/from.go b/pkg/stream/from.go index f1e7886..476b6e9 100644 --- a/pkg/stream/from.go +++ b/pkg/stream/from.go @@ -3,7 +3,7 @@ package stream import ( "context" - "github.com/jpfourny/papaya/pkg/pair" + "github.com/jpfourny/papaya/v2/pkg/pair" ) // FromSlice creates a stream that iterates over the elements of the given slice. diff --git a/pkg/stream/from_test.go b/pkg/stream/from_test.go index 9d73fb2..77a89dc 100644 --- a/pkg/stream/from_test.go +++ b/pkg/stream/from_test.go @@ -6,8 +6,8 @@ import ( "sync" "testing" - "github.com/jpfourny/papaya/internal/assert" - "github.com/jpfourny/papaya/pkg/pair" + "github.com/jpfourny/papaya/v2/internal/assert" + "github.com/jpfourny/papaya/v2/pkg/pair" ) func TestFromSlice(t *testing.T) { diff --git a/pkg/stream/generate_test.go b/pkg/stream/generate_test.go index 0e916b9..f673c3a 100644 --- a/pkg/stream/generate_test.go +++ b/pkg/stream/generate_test.go @@ -1,7 +1,7 @@ package stream import ( - "github.com/jpfourny/papaya/internal/assert" + "github.com/jpfourny/papaya/v2/internal/assert" "math/rand" "testing" ) diff --git a/pkg/stream/group.go b/pkg/stream/group.go index 7f5b55a..c31af1a 100644 --- a/pkg/stream/group.go +++ b/pkg/stream/group.go @@ -1,11 +1,11 @@ package stream import ( - "github.com/jpfourny/papaya/internal/kvstore" - "github.com/jpfourny/papaya/pkg/cmp" - "github.com/jpfourny/papaya/pkg/constraint" - "github.com/jpfourny/papaya/pkg/pair" - "github.com/jpfourny/papaya/pkg/stream/mapper" + "github.com/jpfourny/papaya/v2/internal/kvstore" + "github.com/jpfourny/papaya/v2/pkg/cmp" + "github.com/jpfourny/papaya/v2/pkg/constraint" + "github.com/jpfourny/papaya/v2/pkg/pair" + "github.com/jpfourny/papaya/v2/pkg/stream/mapper" ) // GroupByKey returns a stream that values key-value pairs by key. diff --git a/pkg/stream/group_test.go b/pkg/stream/group_test.go index ad4c696..fc47c1d 100644 --- a/pkg/stream/group_test.go +++ b/pkg/stream/group_test.go @@ -4,8 +4,8 @@ import ( "reflect" "testing" - "github.com/jpfourny/papaya/pkg/cmp" - "github.com/jpfourny/papaya/pkg/pair" + "github.com/jpfourny/papaya/v2/pkg/cmp" + "github.com/jpfourny/papaya/v2/pkg/pair" ) func TestGroupByKey(t *testing.T) { diff --git a/pkg/stream/iter.go b/pkg/stream/iter.go index 0e683d6..890cf69 100644 --- a/pkg/stream/iter.go +++ b/pkg/stream/iter.go @@ -1,7 +1,7 @@ package stream import ( - "github.com/jpfourny/papaya/pkg/pair" + "github.com/jpfourny/papaya/v2/pkg/pair" "iter" ) diff --git a/pkg/stream/iter_test.go b/pkg/stream/iter_test.go index 3bce647..a9d8f96 100644 --- a/pkg/stream/iter_test.go +++ b/pkg/stream/iter_test.go @@ -1,8 +1,8 @@ package stream import ( - "github.com/jpfourny/papaya/internal/assert" - "github.com/jpfourny/papaya/pkg/pair" + "github.com/jpfourny/papaya/v2/internal/assert" + "github.com/jpfourny/papaya/v2/pkg/pair" "maps" "slices" "testing" @@ -23,5 +23,5 @@ func TestFromIterSeq2(t *testing.T) { pair.Of(3, 4), pair.Of(5, 6), } - assert.ElementsMatch(t, got, want) + assert.ElementsMatchAnyOrder(t, got, want) } diff --git a/pkg/stream/map.go b/pkg/stream/map.go index e14bc4b..2c30712 100644 --- a/pkg/stream/map.go +++ b/pkg/stream/map.go @@ -1,7 +1,7 @@ package stream import ( - "github.com/jpfourny/papaya/pkg/opt" + "github.com/jpfourny/papaya/v2/pkg/opt" ) // Mapper represents a function that transforms an input of type E to an output of type F. @@ -66,18 +66,11 @@ type StreamMapper[E, F any] func(from E) (to Stream[F]) // out := stream.DebugString(s) // "<1, 1, 2, 2, 3, 3>" func FlatMap[E, F any](s Stream[E], m StreamMapper[E, F]) Stream[F] { return func(yield Consumer[F]) { - stopped := false - yield2 := func(f F) bool { // Stop-sensing consumer. - if yield(f) { - return true - } - stopped = true - return false - } + yield2, stopped := stopSensingConsumer(yield) s(func(e E) bool { m(e)(yield2) - if stopped { + if *stopped { return false // Consumer saw enough. } return true @@ -104,17 +97,10 @@ type SliceMapper[E, F any] func(from E) (to []F) func FlatMapSlice[E, F any](s Stream[E], m SliceMapper[E, F]) Stream[F] { return func(yield Consumer[F]) { s(func(e E) bool { - stopped := false - yield2 := func(f F) bool { // Stop-sensing consumer. - if yield(f) { - return true - } - stopped = true - return false - } + yield2, stopped := stopSensingConsumer(yield) FromSlice(m(e))(yield2) - if stopped { + if *stopped { return false // Consumer saw enough. } return true diff --git a/pkg/stream/map_test.go b/pkg/stream/map_test.go index 636f703..53816c5 100644 --- a/pkg/stream/map_test.go +++ b/pkg/stream/map_test.go @@ -2,10 +2,10 @@ package stream import ( "fmt" - "github.com/jpfourny/papaya/pkg/stream/mapper" + "github.com/jpfourny/papaya/v2/pkg/stream/mapper" "testing" - "github.com/jpfourny/papaya/internal/assert" + "github.com/jpfourny/papaya/v2/internal/assert" ) func TestMap(t *testing.T) { diff --git a/pkg/stream/mapper/condition.go b/pkg/stream/mapper/condition.go index 24c9fe9..af40cea 100644 --- a/pkg/stream/mapper/condition.go +++ b/pkg/stream/mapper/condition.go @@ -1,6 +1,6 @@ package mapper -import "github.com/jpfourny/papaya/pkg/opt" +import "github.com/jpfourny/papaya/v2/pkg/opt" // If returns a function that accepts a value of any type E and returns the result of calling the `ifTrue` function as an opt.Optional or an empty opt, if the `cond` function returns false. // diff --git a/pkg/stream/mapper/condition_test.go b/pkg/stream/mapper/condition_test.go index aec32fa..66e56b4 100644 --- a/pkg/stream/mapper/condition_test.go +++ b/pkg/stream/mapper/condition_test.go @@ -1,10 +1,10 @@ package mapper import ( - "github.com/jpfourny/papaya/pkg/stream/pred" + "github.com/jpfourny/papaya/v2/pkg/stream/pred" "testing" - "github.com/jpfourny/papaya/pkg/opt" + "github.com/jpfourny/papaya/v2/pkg/opt" ) func TestIf(t *testing.T) { diff --git a/pkg/stream/mapper/convert.go b/pkg/stream/mapper/convert.go index b35184a..bcf8550 100644 --- a/pkg/stream/mapper/convert.go +++ b/pkg/stream/mapper/convert.go @@ -1,6 +1,6 @@ package mapper -import "github.com/jpfourny/papaya/pkg/constraint" +import "github.com/jpfourny/papaya/v2/pkg/constraint" // BoolToBool returns a function that accepts a value of boolean type E and returns a value of boolean type F. func BoolToBool[E constraint.Boolean, F constraint.Boolean]() func(E) F { diff --git a/pkg/stream/mapper/format.go b/pkg/stream/mapper/format.go index 9b3b39b..3160dcd 100644 --- a/pkg/stream/mapper/format.go +++ b/pkg/stream/mapper/format.go @@ -4,7 +4,7 @@ import ( "fmt" "strconv" - "github.com/jpfourny/papaya/pkg/constraint" + "github.com/jpfourny/papaya/v2/pkg/constraint" ) // Sprint returns a function that accepts a value of any type and returns the result of calling fmt.Sprint on it. diff --git a/pkg/stream/mapper/misc.go b/pkg/stream/mapper/misc.go index bf9494c..634397e 100644 --- a/pkg/stream/mapper/misc.go +++ b/pkg/stream/mapper/misc.go @@ -1,6 +1,6 @@ package mapper -import "github.com/jpfourny/papaya/pkg/constraint" +import "github.com/jpfourny/papaya/v2/pkg/constraint" // Identity returns a function that accepts a value of any type E and returns that value. func Identity[E any]() func(E) E { diff --git a/pkg/stream/mapper/parse.go b/pkg/stream/mapper/parse.go index 5de5912..c819dd4 100644 --- a/pkg/stream/mapper/parse.go +++ b/pkg/stream/mapper/parse.go @@ -4,8 +4,8 @@ import ( "strconv" "time" - "github.com/jpfourny/papaya/pkg/constraint" - "github.com/jpfourny/papaya/pkg/opt" + "github.com/jpfourny/papaya/v2/pkg/constraint" + "github.com/jpfourny/papaya/v2/pkg/opt" ) // TryParseBool returns a function that accepts a value of any string type and returns the result of calling strconv.ParseBool on it. diff --git a/pkg/stream/mapper/parse_test.go b/pkg/stream/mapper/parse_test.go index f9eae35..f38edb1 100644 --- a/pkg/stream/mapper/parse_test.go +++ b/pkg/stream/mapper/parse_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - "github.com/jpfourny/papaya/pkg/opt" + "github.com/jpfourny/papaya/v2/pkg/opt" ) func TestTryParseBool(t *testing.T) { diff --git a/pkg/stream/mapper/pointer.go b/pkg/stream/mapper/pointer.go index fedd0d7..021455e 100644 --- a/pkg/stream/mapper/pointer.go +++ b/pkg/stream/mapper/pointer.go @@ -1,8 +1,8 @@ package mapper import ( - "github.com/jpfourny/papaya/pkg/opt" - "github.com/jpfourny/papaya/pkg/ptr" + "github.com/jpfourny/papaya/v2/pkg/opt" + "github.com/jpfourny/papaya/v2/pkg/ptr" ) // PtrRef returns a function that accepts a value of type V and returns a pointer to a copy of the value (on the heap). diff --git a/pkg/stream/mapper/pointer_test.go b/pkg/stream/mapper/pointer_test.go index 0aa6dd1..172270e 100644 --- a/pkg/stream/mapper/pointer_test.go +++ b/pkg/stream/mapper/pointer_test.go @@ -3,8 +3,8 @@ package mapper import ( "testing" - "github.com/jpfourny/papaya/pkg/opt" - "github.com/jpfourny/papaya/pkg/ptr" + "github.com/jpfourny/papaya/v2/pkg/opt" + "github.com/jpfourny/papaya/v2/pkg/ptr" ) func TestPtrRef(t *testing.T) { diff --git a/pkg/stream/match.go b/pkg/stream/match.go index 8ab1ba0..3b26d76 100644 --- a/pkg/stream/match.go +++ b/pkg/stream/match.go @@ -2,9 +2,9 @@ package stream import ( "context" - "github.com/jpfourny/papaya/pkg/cmp" - "github.com/jpfourny/papaya/pkg/opt" - "github.com/jpfourny/papaya/pkg/stream/pred" + "github.com/jpfourny/papaya/v2/pkg/cmp" + "github.com/jpfourny/papaya/v2/pkg/opt" + "github.com/jpfourny/papaya/v2/pkg/stream/pred" ) // AnyMatch returns true if any element in the stream matches the given Predicate. diff --git a/pkg/stream/match_test.go b/pkg/stream/match_test.go index 1e36688..f2cf06e 100644 --- a/pkg/stream/match_test.go +++ b/pkg/stream/match_test.go @@ -1,7 +1,7 @@ package stream import ( - "github.com/jpfourny/papaya/pkg/cmp" + "github.com/jpfourny/papaya/v2/pkg/cmp" "testing" ) diff --git a/pkg/stream/misc.go b/pkg/stream/misc.go index 6a773c2..b3c8346 100644 --- a/pkg/stream/misc.go +++ b/pkg/stream/misc.go @@ -1,10 +1,10 @@ package stream import ( - "github.com/jpfourny/papaya/pkg/constraint" - "github.com/jpfourny/papaya/pkg/opt" - "github.com/jpfourny/papaya/pkg/stream/mapper" - "github.com/jpfourny/papaya/pkg/stream/pred" + "github.com/jpfourny/papaya/v2/pkg/constraint" + "github.com/jpfourny/papaya/v2/pkg/opt" + "github.com/jpfourny/papaya/v2/pkg/stream/mapper" + "github.com/jpfourny/papaya/v2/pkg/stream/pred" "strings" ) @@ -165,21 +165,14 @@ func StringJoin(s Stream[string], sep string) string { // out := stream.DebugString(s) // "<1, 2, 3, 0, 0>" func Pad[E any](s Stream[E], pad E, length int) Stream[E] { return func(yield Consumer[E]) { - stopped := false - yield2 := func(e E) bool { // Stop-sensing consumer. - if yield(e) { - return true - } - stopped = true - return false - } + yield2, stopped := stopSensingConsumer(yield) i := 0 s(func(e E) bool { i++ return yield2(e) }) - if stopped { + if *stopped { return // Consumer saw enough. } for ; i < length; i++ { diff --git a/pkg/stream/misc_test.go b/pkg/stream/misc_test.go index 0871f7e..f7597fd 100644 --- a/pkg/stream/misc_test.go +++ b/pkg/stream/misc_test.go @@ -1,12 +1,12 @@ package stream import ( - "github.com/jpfourny/papaya/pkg/stream/mapper" - "github.com/jpfourny/papaya/pkg/stream/pred" + "github.com/jpfourny/papaya/v2/pkg/stream/mapper" + "github.com/jpfourny/papaya/v2/pkg/stream/pred" "testing" - "github.com/jpfourny/papaya/internal/assert" - "github.com/jpfourny/papaya/pkg/opt" + "github.com/jpfourny/papaya/v2/internal/assert" + "github.com/jpfourny/papaya/v2/pkg/opt" ) func TestForEach(t *testing.T) { diff --git a/pkg/stream/pred/compare.go b/pkg/stream/pred/compare.go index b8ca780..f1b1165 100644 --- a/pkg/stream/pred/compare.go +++ b/pkg/stream/pred/compare.go @@ -4,8 +4,8 @@ import ( "math" "reflect" - "github.com/jpfourny/papaya/pkg/cmp" - "github.com/jpfourny/papaya/pkg/constraint" + "github.com/jpfourny/papaya/v2/pkg/cmp" + "github.com/jpfourny/papaya/v2/pkg/constraint" ) // Equal returns a function that returns true if the provided value is equal to the provided want value. diff --git a/pkg/stream/pred/compare_test.go b/pkg/stream/pred/compare_test.go index b76d371..e964ae3 100644 --- a/pkg/stream/pred/compare_test.go +++ b/pkg/stream/pred/compare_test.go @@ -3,7 +3,7 @@ package pred import ( "testing" - "github.com/jpfourny/papaya/pkg/ptr" + "github.com/jpfourny/papaya/v2/pkg/ptr" ) func TestEqual(t *testing.T) { diff --git a/pkg/stream/pred/in.go b/pkg/stream/pred/in.go index b61f705..f2226d5 100644 --- a/pkg/stream/pred/in.go +++ b/pkg/stream/pred/in.go @@ -1,6 +1,6 @@ package pred -import "github.com/jpfourny/papaya/pkg/cmp" +import "github.com/jpfourny/papaya/v2/pkg/cmp" // In returns a function that returns true if the provided value is equal to any of the provided want values. // It uses the == operator to compare values. diff --git a/pkg/stream/pred/zero_test.go b/pkg/stream/pred/zero_test.go index f70b6bc..74432c6 100644 --- a/pkg/stream/pred/zero_test.go +++ b/pkg/stream/pred/zero_test.go @@ -3,8 +3,8 @@ package pred import ( "testing" - "github.com/jpfourny/papaya/pkg/pair" - "github.com/jpfourny/papaya/pkg/ptr" + "github.com/jpfourny/papaya/v2/pkg/pair" + "github.com/jpfourny/papaya/v2/pkg/ptr" ) func TestNil(t *testing.T) { diff --git a/pkg/stream/reducer/reducer.go b/pkg/stream/reducer/reducer.go index 3b2fcb6..d9688db 100644 --- a/pkg/stream/reducer/reducer.go +++ b/pkg/stream/reducer/reducer.go @@ -1,8 +1,8 @@ package reducer import ( - "github.com/jpfourny/papaya/pkg/cmp" - "github.com/jpfourny/papaya/pkg/constraint" + "github.com/jpfourny/papaya/v2/pkg/cmp" + "github.com/jpfourny/papaya/v2/pkg/constraint" ) // Sum returns a function that computes the sum of two given values. diff --git a/pkg/stream/set.go b/pkg/stream/set.go index 00d4783..3853950 100644 --- a/pkg/stream/set.go +++ b/pkg/stream/set.go @@ -1,8 +1,8 @@ package stream import ( - "github.com/jpfourny/papaya/internal/kvstore" - "github.com/jpfourny/papaya/pkg/cmp" + "github.com/jpfourny/papaya/v2/internal/kvstore" + "github.com/jpfourny/papaya/v2/pkg/cmp" ) // Union combines multiple streams into a single stream (concatenation). @@ -15,18 +15,11 @@ import ( // out := stream.DebugString(s) // "<1, 2, 3, 4, 4, 5, 6>" func Union[E any](ss ...Stream[E]) Stream[E] { return func(yield Consumer[E]) { - stopped := false - yield2 := func(e E) bool { // Stop-sensing consumer. - if yield(e) { - return true - } - stopped = true - return false - } + yield2, stopped := stopSensingConsumer(yield) for _, s := range ss { s(yield2) - if stopped { + if *stopped { return // Consumer saw enough. } } diff --git a/pkg/stream/set_test.go b/pkg/stream/set_test.go index 6f69940..6d71b3f 100644 --- a/pkg/stream/set_test.go +++ b/pkg/stream/set_test.go @@ -1,10 +1,10 @@ package stream import ( - "github.com/jpfourny/papaya/pkg/cmp" + "github.com/jpfourny/papaya/v2/pkg/cmp" "testing" - "github.com/jpfourny/papaya/internal/assert" + "github.com/jpfourny/papaya/v2/internal/assert" ) func TestUnion(t *testing.T) { diff --git a/pkg/stream/sort.go b/pkg/stream/sort.go index c9a64df..301f015 100644 --- a/pkg/stream/sort.go +++ b/pkg/stream/sort.go @@ -1,8 +1,9 @@ package stream import ( - "github.com/jpfourny/papaya/pkg/cmp" - "github.com/jpfourny/papaya/pkg/constraint" + "github.com/jpfourny/papaya/v2/pkg/cmp" + "github.com/jpfourny/papaya/v2/pkg/constraint" + "github.com/jpfourny/papaya/v2/pkg/pair" "slices" ) @@ -41,3 +42,113 @@ func SortBy[E any](s Stream[E], compare cmp.Comparer[E]) Stream[E] { FromSlice(sl)(yield) } } + +// SortKeyAsc returns a stream that sorts the elements by key in ascending order. +// The elements must be pairs of key and value. +// The keys must implement the Ordered interface. +// +// Example usage: +// +// s := stream.SortKeyAsc( +// stream.Of( +// pair.Of(3, "c"), +// pair.Of(1, "a"), +// pair.Of(2, "b"), +// ), +// ) +// out := stream.DebugString(s) // "<1:a, 2:b, 3:c>" +func SortKeyAsc[K constraint.Ordered, V any](s Stream[pair.Pair[K, V]]) Stream[pair.Pair[K, V]] { + return SortKeyBy(s, cmp.Natural[K]()) +} + +// SortKeyDesc returns a stream that sorts the elements by key in descending order. +// The elements must be pairs of key and value. +// The keys must implement the Ordered interface. +// +// Example usage: +// +// s := stream.SortKeyDesc( +// stream.Of( +// pair.Of(3, "c"), +// pair.Of(1, "a"), +// pair.Of(2, "b"), +// ), +// ) +// out := stream.DebugString(s) // "<3:c, 2:b, 1:a>" +func SortKeyDesc[K constraint.Ordered, V any](s Stream[pair.Pair[K, V]]) Stream[pair.Pair[K, V]] { + return SortKeyBy(s, cmp.Reverse[K]()) +} + +// SortKeyBy returns a stream that sorts the elements by key using the given cmp.Comparer. +// The elements must be pairs of key and value. +// The order of the elements is determined by the comparer. +// +// Example usage: +// +// s := stream.SortKeyBy( +// stream.Of( +// pair.Of(3, "c"), +// pair.Of(1, "a"), +// pair.Of(2, "b"), +// ), +// cmp.Natural[int](), +// ) +// out := stream.DebugString(s) // "<1:a, 2:b, 3:c>" +func SortKeyBy[K any, V any](s Stream[pair.Pair[K, V]], compare cmp.Comparer[K]) Stream[pair.Pair[K, V]] { + return SortBy(s, cmp.ComparingBy(pair.Pair[K, V].First, compare)) +} + +// SortValueAsc returns a stream that sorts the elements by value in ascending order. +// The elements must be pairs of key and value. +// The values must implement the Ordered interface. +// +// Example usage: +// +// s := stream.SortValueAsc( +// stream.Of( +// pair.Of("c", 3), +// pair.Of("a", 1), +// pair.Of("b", 2), +// ), +// ) +// out := stream.DebugString(s) // "a:1, b:2, c:3" +func SortValueAsc[K any, V constraint.Ordered](s Stream[pair.Pair[K, V]]) Stream[pair.Pair[K, V]] { + return SortValueBy(s, cmp.Natural[V]()) +} + +// SortValueDesc returns a stream that sorts the elements by value in descending order. +// The elements must be pairs of key and value. +// The values must implement the Ordered interface. +// +// Example usage: +// +// s := stream.SortValueDesc( +// stream.Of( +// pair.Of("c", 3), +// pair.Of("a", 1), +// pair.Of("b", 2), +// ), +// ) +// out := stream.DebugString(s) // "c:3, b:2, a:1" +func SortValueDesc[K any, V constraint.Ordered](s Stream[pair.Pair[K, V]]) Stream[pair.Pair[K, V]] { + return SortValueBy(s, cmp.Reverse[V]()) +} + +// SortValueBy returns a stream that sorts the elements by value using the given cmp.Comparer. +// The elements must be pairs of key and value. +// The order of the elements is determined by the comparer. +// +// Example usage: +// +// s := stream.SortValueBy( +// stream.Of( +// pair.Of("c", 3), +// pair.Of("a", 1), +// pair.Of("b", 2), +// ), +// cmp.Natural[int](), +// ) +// out := stream.DebugString(s) // "a:1, b:2, c:3" +func SortValueBy[K any, V any](s Stream[pair.Pair[K, V]], compare cmp.Comparer[V]) Stream[pair.Pair[K, V]] { + return SortBy(s, cmp.ComparingBy(pair.Pair[K, V].Second, compare)) +} diff --git a/pkg/stream/sort_test.go b/pkg/stream/sort_test.go index b1803e3..3c48a84 100644 --- a/pkg/stream/sort_test.go +++ b/pkg/stream/sort_test.go @@ -1,7 +1,8 @@ package stream import ( - "github.com/jpfourny/papaya/internal/assert" + "github.com/jpfourny/papaya/v2/internal/assert" + "github.com/jpfourny/papaya/v2/pkg/pair" "testing" ) @@ -27,3 +28,18 @@ func TestSortBy(t *testing.T) { want := []int{1, 2, 3} assert.ElementsMatch(t, got, want) } + +func TestSortKeyAsc(t *testing.T) { + s := SortKeyAsc(Of( + pair.Of(3, "c"), + pair.Of(1, "a"), + pair.Of(2, "b"), + )) + got := CollectSlice(s) + want := []pair.Pair[int, string]{ + pair.Of(1, "a"), + pair.Of(2, "b"), + pair.Of(3, "c"), + } + assert.ElementsMatch(t, got, want) +} diff --git a/pkg/stream/stream.go b/pkg/stream/stream.go index cc6c6f7..b52552c 100644 --- a/pkg/stream/stream.go +++ b/pkg/stream/stream.go @@ -10,11 +10,6 @@ package stream // If the stream is exhausted, it must return true. type Stream[E any] func(c Consumer[E]) -// Consumer represents a function that accepts a yielded element of type E and returns a boolean value. -// The boolean value indicates whether the consumer wishes to continue accepting elements. -// If the consumer returns false, the caller must stop yielding elements. -type Consumer[E any] func(yield E) (cont bool) - // Empty returns a stream that does not contain any elements. // It always returns true when invoked with a consumer. // diff --git a/pkg/stream/stream_test.go b/pkg/stream/stream_test.go index 9c8a672..3075a5d 100644 --- a/pkg/stream/stream_test.go +++ b/pkg/stream/stream_test.go @@ -3,7 +3,7 @@ package stream import ( "testing" - "github.com/jpfourny/papaya/internal/assert" + "github.com/jpfourny/papaya/v2/internal/assert" ) func TestEmpty(t *testing.T) {