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 <dev@samuel-berthe.fr>
This commit is contained in:
Adam Szaraniec
2026-03-02 19:07:14 +04:00
committed by GitHub
parent 6a9f881ae1
commit 56ef3beaf8
5 changed files with 94 additions and 21 deletions
+2 -21
View File
@@ -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
+40
View File
@@ -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]]
```
+7
View File
@@ -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%.
+24
View File
@@ -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)
}
}
}
+21
View File
@@ -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)
}