diff --git a/constr/doc.go b/constr/doc.go new file mode 100644 index 0000000..d0741ec --- /dev/null +++ b/constr/doc.go @@ -0,0 +1,27 @@ +// Package constr provides a set of constraints for Go generics. +package constr + +// Integer represents a type that is a signed or unsigned integer. +type Integer interface { + Signed | Unsigned +} + +// Signed represents a type that is a signed integer. +type Signed interface { + ~int | ~int8 | ~int16 | ~int32 | ~int64 +} + +// Unsigned represents a type that is an unsigned integer. +type Unsigned interface { + ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 +} + +// Float represents a type that is a floating-point number. +type Float interface { + ~float32 | ~float64 +} + +// Numeric represents a type that is a number. +type Numeric interface { + Integer | Float +} diff --git a/example_test.go b/example_test.go index a322a16..315ace9 100644 --- a/example_test.go +++ b/example_test.go @@ -226,3 +226,12 @@ func ExampleMerge() { // 2: "even" // 10000: "large" } + +func ExampleSum() { + source := []int{1, 2, 3} + target := gog.Sum(source) + fmt.Println(target) + + // Output: + // 6 +} diff --git a/seq/example_test.go b/seq/example_test.go index 63cde9c..dbc3889 100644 --- a/seq/example_test.go +++ b/seq/example_test.go @@ -2,6 +2,7 @@ package seq_test import ( "fmt" + "slices" "github.com/mokiat/gog/seq" ) @@ -81,3 +82,37 @@ func ExampleBatchSlice() { // []string{"2This", "2Is", "2Longer"} // []string{"3Yes"} } + +func ExampleSelect() { + source := slices.Values([]int{1, 2, 3, 4, 5}) + target := seq.Select(source, func(v int) bool { + return v%2 == 0 + }) + for v := range target { + fmt.Println(v) + } + + // Output: + // 2 + // 4 +} + +func ExampleReduce() { + source := slices.Values([]int{1, 2, 3}) + result := seq.Reduce(source, 10, func(acc, v int) int { + return acc + v + }) + fmt.Println(result) + + // Output: + // 16 +} + +func ExampleSum() { + source := slices.Values([]int{1, 2, 3}) + result := seq.Sum(source) + fmt.Println(result) + + // Output: + // 6 +} diff --git a/seq/filter.go b/seq/filter.go new file mode 100644 index 0000000..a590c50 --- /dev/null +++ b/seq/filter.go @@ -0,0 +1,19 @@ +package seq + +import "iter" + +// Select applies the given predicate function to each element of the source +// sequence and returns a new sequence with the elements for which the predicate +// returned true. +func Select[T any](src iter.Seq[T], pred func(T) bool) iter.Seq[T] { + return func(yield func(T) bool) { + for item := range src { + if !pred(item) { + continue + } + if !yield(item) { + return + } + } + } +} diff --git a/seq/filter_test.go b/seq/filter_test.go new file mode 100644 index 0000000..480b701 --- /dev/null +++ b/seq/filter_test.go @@ -0,0 +1,27 @@ +package seq_test + +import ( + "slices" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/mokiat/gog/seq" +) + +var _ = Describe("Filter", func() { + + Describe("Select", func() { + isEven := func(value int) bool { + return value%2 == 0 + } + + It("returns only the elements that match the predicate", func() { + source := slices.Values([]int{1, 2, 3, 4, 5}) + target := seq.Select(source, isEven) + items := slices.Collect(target) + Expect(items).To(Equal([]int{2, 4})) + }) + }) + +}) diff --git a/seq/transform.go b/seq/transform.go index 307f150..04a2c71 100644 --- a/seq/transform.go +++ b/seq/transform.go @@ -1,6 +1,10 @@ package seq -import "iter" +import ( + "iter" + + "github.com/mokiat/gog/constr" +) // Map applies the given transformation function to each element of the source // sequence and returns a new sequence with the results. @@ -65,3 +69,26 @@ func BatchSliceFast[T any](items []T, eqFunc func(items []T, i, j int) bool, max } } } + +// Reduce compacts a sequence into a single value. The provided function is used +// to perform the reduction starting with the initialValue. +func Reduce[T any, S any](src iter.Seq[S], initialValue T, fn func(accum T, valye S) T) T { + accum := initialValue + for v := range src { + accum = fn(accum, v) + } + return accum +} + +// Sum is a convenience function that calculates the sum of all elements in the +// source sequence. +// +// The same can normally be achieved with the Reduce function, but this function +// is simpler to use and faster. +func Sum[T constr.Numeric](src iter.Seq[T]) T { + var result T + for v := range src { + result += v + } + return result +} diff --git a/seq/transform_test.go b/seq/transform_test.go index 93212c4..5052897 100644 --- a/seq/transform_test.go +++ b/seq/transform_test.go @@ -88,4 +88,30 @@ var _ = Describe("Transform", func() { })) }) }) + + Describe("Reduce", func() { + It("reduces a sequence to a single value", func() { + source := slices.Values([]int{1, 2, 3}) + result := seq.Reduce(source, ">", func(accum string, value int) string { + return accum + strconv.Itoa(value) + }) + Expect(result).To(Equal(">123")) + }) + }) + + Describe("Sum", func() { + type IntWrapper int + + It("calculates the sum of the sequence", func() { + source := slices.Values([]IntWrapper{0, 1, 2, 3}) + result := seq.Sum(source) + Expect(result).To(Equal(IntWrapper(6))) + }) + + It("returns the zero value for empty sequence", func() { + source := slices.Values([]int{}) + result := seq.Sum(source) + Expect(result).To(Equal(0)) + }) + }) }) diff --git a/slice.go b/slice.go index 4ac3094..f0309f7 100644 --- a/slice.go +++ b/slice.go @@ -1,6 +1,10 @@ package gog -import "maps" +import ( + "maps" + + "github.com/mokiat/gog/constr" +) // Map can be used to transform one slice into another by providing a // function to do the mapping. @@ -201,3 +205,16 @@ func Merge[K comparable, V any](ms ...map[K]V) map[K]V { } return result } + +// Sum is a convenience function that calculates the sum of all elements in the +// source slice. +// +// The same can normally be achieved with the Reduce function, but this function +// is simpler to use and faster. +func Sum[T constr.Numeric](src []T) T { + var result T + for _, v := range src { + result += v + } + return result +} diff --git a/slice_test.go b/slice_test.go index 32a761c..85e783d 100644 --- a/slice_test.go +++ b/slice_test.go @@ -322,4 +322,20 @@ var _ = Describe("Slice", func() { Expect(result).To(Equal(map[int]string{})) }) }) + + Describe("Sum", func() { + type IntWrapper int + + It("calculates the sum of the slice", func() { + source := []IntWrapper{0, 1, 2, 3} + result := gog.Sum(source) + Expect(result).To(Equal(IntWrapper(6))) + }) + + It("returns the zero value for empty slices", func() { + source := []int{} + result := gog.Sum(source) + Expect(result).To(Equal(0)) + }) + }) })