Skip to content

Commit 853e598

Browse files
mdlayherpull[bot]
authored andcommitted
slices: add Chunk
Chunk returns an iterator over consecutive sub-slices of up to n elements of s. Fixes #53987. Change-Id: I508274eca388db39550eb9e4d8abd5ce68d29d8d Reviewed-on: https://go-review.googlesource.com/c/go/+/562935 Reviewed-by: Cherry Mui <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]> Auto-Submit: Ian Lance Taylor <[email protected]>
1 parent 2d5d032 commit 853e598

File tree

6 files changed

+165
-0
lines changed

6 files changed

+165
-0
lines changed

api/next/53987.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pkg slices, func Chunk[$0 interface{ ~[]$1 }, $1 interface{}]($0, int) iter.Seq[$0] #53987

doc/next/6-stdlib/3-iter.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,5 @@ with iterators:
1919
comparison function.
2020
- [SortedStableFunc](/pkg/slices#SortedStableFunc) is like `SortFunc`
2121
but uses a stable sort algorithm.
22+
- [Chunk](/pkg/slices#Chunk) returns an iterator over consecutive
23+
sub-slices of up to n elements of a slice.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<!-- see ../../3-iter.md -->

src/slices/example_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,3 +384,30 @@ func ExampleRepeat() {
384384
// Output:
385385
// [0 1 2 3 0 1 2 3]
386386
}
387+
388+
func ExampleChunk() {
389+
type Person struct {
390+
Name string
391+
Age int
392+
}
393+
394+
type People []Person
395+
396+
people := People{
397+
{"Gopher", 13},
398+
{"Alice", 20},
399+
{"Bob", 5},
400+
{"Vera", 24},
401+
{"Zac", 15},
402+
}
403+
404+
// Chunk people into []Person 2 elements at a time.
405+
for c := range slices.Chunk(people, 2) {
406+
fmt.Println(c)
407+
}
408+
409+
// Output:
410+
// [{Gopher 13} {Alice 20}]
411+
// [{Bob 5} {Vera 24}]
412+
// [{Zac 15}]
413+
}

src/slices/iter.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,27 @@ func SortedStableFunc[E any](seq iter.Seq[E], cmp func(E, E) int) []E {
8484
SortStableFunc(s, cmp)
8585
return s
8686
}
87+
88+
// Chunk returns an iterator over consecutive sub-slices of up to n elements of s.
89+
// All but the last sub-slice will have size n.
90+
// All sub-slices are clipped to have no capacity beyond the length.
91+
// If s is empty, the sequence is empty: there is no empty slice in the sequence.
92+
// Chunk panics if n is less than 1.
93+
func Chunk[Slice ~[]E, E any](s Slice, n int) iter.Seq[Slice] {
94+
if n < 1 {
95+
panic("cannot be less than 1")
96+
}
97+
98+
return func(yield func(Slice) bool) {
99+
for i := 0; i < len(s); i += n {
100+
// Clamp the last chunk to the slice bound as necessary.
101+
end := min(n, len(s[i:]))
102+
103+
// Set the capacity of each chunk so that appending to a chunk does
104+
// not modify the original slice.
105+
if !yield(s[i : i+end : i+end]) {
106+
return
107+
}
108+
}
109+
}
110+
}

src/slices/iter_test.go

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,3 +182,113 @@ func TestSortedStableFunc(t *testing.T) {
182182
t.Errorf("SortedStableFunc wasn't stable on %d reverse ints", n)
183183
}
184184
}
185+
186+
func TestChunk(t *testing.T) {
187+
cases := []struct {
188+
name string
189+
s []int
190+
n int
191+
chunks [][]int
192+
}{
193+
{
194+
name: "nil",
195+
s: nil,
196+
n: 1,
197+
chunks: nil,
198+
},
199+
{
200+
name: "empty",
201+
s: []int{},
202+
n: 1,
203+
chunks: nil,
204+
},
205+
{
206+
name: "short",
207+
s: []int{1, 2},
208+
n: 3,
209+
chunks: [][]int{{1, 2}},
210+
},
211+
{
212+
name: "one",
213+
s: []int{1, 2},
214+
n: 2,
215+
chunks: [][]int{{1, 2}},
216+
},
217+
{
218+
name: "even",
219+
s: []int{1, 2, 3, 4},
220+
n: 2,
221+
chunks: [][]int{{1, 2}, {3, 4}},
222+
},
223+
{
224+
name: "odd",
225+
s: []int{1, 2, 3, 4, 5},
226+
n: 2,
227+
chunks: [][]int{{1, 2}, {3, 4}, {5}},
228+
},
229+
}
230+
231+
for _, tc := range cases {
232+
t.Run(tc.name, func(t *testing.T) {
233+
var chunks [][]int
234+
for c := range Chunk(tc.s, tc.n) {
235+
chunks = append(chunks, c)
236+
}
237+
238+
if !chunkEqual(chunks, tc.chunks) {
239+
t.Errorf("Chunk(%v, %d) = %v, want %v", tc.s, tc.n, chunks, tc.chunks)
240+
}
241+
242+
if len(chunks) == 0 {
243+
return
244+
}
245+
246+
// Verify that appending to the end of the first chunk does not
247+
// clobber the beginning of the next chunk.
248+
s := Clone(tc.s)
249+
chunks[0] = append(chunks[0], -1)
250+
if !Equal(s, tc.s) {
251+
t.Errorf("slice was clobbered: %v, want %v", s, tc.s)
252+
}
253+
})
254+
}
255+
}
256+
257+
func TestChunkPanics(t *testing.T) {
258+
for _, test := range []struct {
259+
name string
260+
x []struct{}
261+
n int
262+
}{
263+
{
264+
name: "cannot be less than 1",
265+
x: make([]struct{}, 0),
266+
n: 0,
267+
},
268+
} {
269+
if !panics(func() { _ = Chunk(test.x, test.n) }) {
270+
t.Errorf("Chunk %s: got no panic, want panic", test.name)
271+
}
272+
}
273+
}
274+
275+
func TestChunkRange(t *testing.T) {
276+
// Verify Chunk iteration can be stopped.
277+
var got [][]int
278+
for c := range Chunk([]int{1, 2, 3, 4, -100}, 2) {
279+
if len(got) == 2 {
280+
// Found enough values, break early.
281+
break
282+
}
283+
284+
got = append(got, c)
285+
}
286+
287+
if want := [][]int{{1, 2}, {3, 4}}; !chunkEqual(got, want) {
288+
t.Errorf("Chunk iteration did not stop, got %v, want %v", got, want)
289+
}
290+
}
291+
292+
func chunkEqual[Slice ~[]E, E comparable](s1, s2 []Slice) bool {
293+
return EqualFunc(s1, s2, Equal[Slice])
294+
}

0 commit comments

Comments
 (0)