From 56ef3beaf8adfea1908b094e49b3b639ea604aab Mon Sep 17 00:00:00 2001 From: Adam Szaraniec Date: Mon, 2 Mar 2026 19:07:14 +0400 Subject: [PATCH] feat: support for buffer iterator (#824) * support for buffer iterator * Code review fix * fix: transforming the Buffer helper into a pull-based operator (it receives an iterator instead of channel) * doc(it): adding loit.Buffer to doc --------- Co-authored-by: Samuel Berthe --- docs/CLAUDE.md | 23 ++-------------------- docs/data/it-buffer.md | 40 +++++++++++++++++++++++++++++++++++++++ docs/docs/contributing.md | 7 +++++++ it/seq.go | 24 +++++++++++++++++++++++ it/seq_test.go | 21 ++++++++++++++++++++ 5 files changed, 94 insertions(+), 21 deletions(-) create mode 100644 docs/data/it-buffer.md diff --git a/docs/CLAUDE.md b/docs/CLAUDE.md index 276812a..47c16af 100644 --- a/docs/CLAUDE.md +++ b/docs/CLAUDE.md @@ -88,6 +88,7 @@ Use `variantHelpers` for different versions of the **same helper function**: ```yaml variantHelpers: - core#slice#map # func Map[T, R]([]T, func(T, int) R) []R + - core#slice#maperr # func MapErr[T, R]([]T, func(T, int) (R, error)) ([]R, error) - core#slice#mapi # func MapI[T, R]([]T, func(T, int) R) []R (with index) - core#slice#mapwithcontext # func MapWithContext[T, R]([]T, func(T, int, context.Context) R, context.Context) []R ``` @@ -194,6 +195,7 @@ Use these established subCategories: - For function variants, use consistent suffixes: - `F` suffix for function-based versions (lazy evaluation) - `I` suffix for variants having `index int` argument in predicate callback + - `Err` suffix for variants returning an error in predicate callback - `WithContext` suffix when context.Context is provided - `X` suffix for helpers with varying arguments (eg: MustX: Must2, Must3, Must4...) @@ -211,27 +213,6 @@ Add these examples in the source code comments, on top of helpers, with a syntax If the documentation is created at the same time of the helper source code, then the Go playground execution might fail, since we need to merge+release the source code first to make this new helper available to Go playground compiler. In that case, skip the creation of the example and set no URL. -## Validation Scripts - -Run these scripts to validate your documentation: - -```bash -# Check cross-references -node scripts/check-cross-references.js - -# Check for duplicates in categories -node scripts/check-duplicates-in-category.js - -# Check filename matches frontmatter -node scripts/check-filename-matches-frontmatter.js - -# Check for similar existing helpers -node scripts/check-similar-exists.js - -# Check for similar keys in directory -node scripts/check-similar-keys-exist-in-directory.js -``` - ## Example: Complete File ```yaml diff --git a/docs/data/it-buffer.md b/docs/data/it-buffer.md new file mode 100644 index 0000000..e7f862d --- /dev/null +++ b/docs/data/it-buffer.md @@ -0,0 +1,40 @@ +--- +name: Buffer +slug: buffer +sourceRef: it/seq.go#L1162 +category: it +subCategory: sequence +signatures: + - "func Buffer[T any](seq iter.Seq[T], size int) iter.Seq[[]T]" +playUrl: "" +variantHelpers: + - it#sequence#buffer +similarHelpers: + - it#sequence#chunk + - it#sequence#sliding + - it#sequence#window +position: 65 +--- + +Returns a sequence of slices, each containing up to size items read from the sequence. +The last slice may be smaller if the sequence closes before filling the buffer. + +Examples: + +```go +seq := func(yield func(int) bool) { + _ = yield(1) + _ = yield(2) + _ = yield(3) + _ = yield(4) + _ = yield(5) + _ = yield(6) + _ = yield(7) +} +buffers := it.Buffer(seq, 3) +var result [][]int +for buffer := range buffers { + result = append(result, buffer) +} +// result contains [[1 2 3] [4 5 6] [7]] +``` diff --git a/docs/docs/contributing.md b/docs/docs/contributing.md index b863a16..0973ba4 100644 --- a/docs/docs/contributing.md +++ b/docs/docs/contributing.md @@ -24,6 +24,13 @@ Functions use `~[]T` constraints to accept any slice type, including named slice ## Variants When applicable, some functions can be added to sub-package as well: `mutable`, `it` and `parallel`. Add a documentation for each helper. +For function variants, use consistent suffixes: +- `F` suffix for function-based versions (lazy evaluation) +- `I` suffix for variants having `index int` argument in predicate callback +- `Err` suffix for variants returning an error in predicate callback +- `WithContext` suffix when context.Context is provided +- `X` suffix for helpers with varying arguments (eg: MustX: Must2, Must3, Must4...) + ## Testing We try to maintain code coverage above 90%. diff --git a/it/seq.go b/it/seq.go index f3f81dc..11b8bae 100644 --- a/it/seq.go +++ b/it/seq.go @@ -1158,3 +1158,27 @@ func TrimSuffix[T comparable, I ~func(func(T) bool)](collection I, suffix []T) I } } } + +// Buffer returns a sequence of slices, each containing up to size items read from the channel. +// The last slice may be smaller if the channel closes before filling the buffer. +func Buffer[T any](seq iter.Seq[T], size int) iter.Seq[[]T] { + return func(yield func([]T) bool) { + buffer := make([]T, 0, size) + + seq(func(v T) bool { + buffer = append(buffer, v) + if len(buffer) < size { + return true // keep pulling + } + // Buffer full, yield it + result := buffer + buffer = make([]T, 0, size) // allocate new buffer + return yield(result) // false = stop, true = continue + }) + + // Yield remaining partial buffer + if len(buffer) > 0 { + yield(buffer) + } + } +} diff --git a/it/seq_test.go b/it/seq_test.go index 06d43ae..0a3281c 100644 --- a/it/seq_test.go +++ b/it/seq_test.go @@ -1799,3 +1799,24 @@ func TestTrimSuffix(t *testing.T) { actual = TrimSuffix(values("a", "b", "c", "d", "e", "f", "g"), []string{}) is.Equal([]string{"a", "b", "c", "d", "e", "f", "g"}, slices.Collect(actual)) } + +func TestBuffer(t *testing.T) { + t.Parallel() + is := assert.New(t) + + // full batches + batches := slices.Collect(Buffer(RangeFrom(1, 6), 2)) + is.Equal([][]int{{1, 2}, {3, 4}, {5, 6}}, batches) + + // partial last batch + batches2 := slices.Collect(Buffer(RangeFrom(1, 5), 2)) + is.Equal([][]int{{1, 2}, {3, 4}, {5}}, batches2) + + // empty channel + batches3 := slices.Collect(Buffer(RangeFrom(1, 0), 2)) + is.Empty(batches3) + + // stop after first batch (early termination) + batches4 := slices.Collect(Take(Buffer(RangeFrom(1, 6), 2), 1)) + is.Equal([][]int{{1, 2}}, batches4) +}