From f94d82b2c03c756f1d8893dc0282e9608e7d32a1 Mon Sep 17 00:00:00 2001 From: Jes Cok Date: Fri, 15 Mar 2024 13:47:53 +0800 Subject: [PATCH] slices: add func Repeat Fixes #65238 Change-Id: I32ae4d922788cc6fbbe80f5b558a075951e3c892 Reviewed-on: https://go-review.googlesource.com/c/go/+/571895 Reviewed-by: David Chase Reviewed-by: Keith Randall Reviewed-by: Keith Randall LUCI-TryBot-Result: Go LUCI --- api/next/65238.txt | 1 + doc/next/6-stdlib/99-minor/slices/65238.md | 2 + src/slices/slices.go | 24 +++++++ src/slices/slices_test.go | 74 ++++++++++++++++++++++ 4 files changed, 101 insertions(+) create mode 100644 api/next/65238.txt create mode 100644 doc/next/6-stdlib/99-minor/slices/65238.md diff --git a/api/next/65238.txt b/api/next/65238.txt new file mode 100644 index 00000000000000..e04939e859ed6a --- /dev/null +++ b/api/next/65238.txt @@ -0,0 +1 @@ +pkg slices, func Repeat[$0 interface{ ~[]$1 }, $1 interface{}]($0, int) $0 #65238 diff --git a/doc/next/6-stdlib/99-minor/slices/65238.md b/doc/next/6-stdlib/99-minor/slices/65238.md new file mode 100644 index 00000000000000..9204eb58a9accd --- /dev/null +++ b/doc/next/6-stdlib/99-minor/slices/65238.md @@ -0,0 +1,2 @@ +The [`Repeat`](/pkg/slices#Repeat) function returns a new slice +that repeats the provided slice the given number of times. diff --git a/src/slices/slices.go b/src/slices/slices.go index 326584064c0c1c..271e8cb3255927 100644 --- a/src/slices/slices.go +++ b/src/slices/slices.go @@ -7,6 +7,7 @@ package slices import ( "cmp" + "math/bits" "unsafe" ) @@ -474,3 +475,26 @@ func Concat[S ~[]E, E any](slices ...S) S { } return newslice } + +// Repeat returns a new slice that repeats the provided slice the given number of times. +// The result has length and capacity len(x) * count. +// The result is never nil. +// Repeat panics if count is negative or if the result of (len(x) * count) +// overflows. +func Repeat[S ~[]E, E any](x S, count int) S { + if count < 0 { + panic("cannot be negative") + } + + const maxInt = ^uint(0) >> 1 + if hi, lo := bits.Mul(uint(len(x)), uint(count)); hi > 0 || lo > maxInt { + panic("the result of (len(x) * count) overflows") + } + + newslice := make(S, len(x)*count) + n := copy(newslice, x) + for n < len(newslice) { + n += copy(newslice[n:], newslice[:n]) + } + return newslice +} diff --git a/src/slices/slices_test.go b/src/slices/slices_test.go index 4b5f0355df2af7..55de2f57d06db1 100644 --- a/src/slices/slices_test.go +++ b/src/slices/slices_test.go @@ -1335,3 +1335,77 @@ func TestConcat_too_large(t *testing.T) { } } } + +func TestRepeat(t *testing.T) { + // normal cases + for _, tc := range []struct { + x []int + count int + want []int + }{ + {x: []int(nil), count: 0, want: []int{}}, + {x: []int(nil), count: 1, want: []int{}}, + {x: []int(nil), count: math.MaxInt, want: []int{}}, + {x: []int{}, count: 0, want: []int{}}, + {x: []int{}, count: 1, want: []int{}}, + {x: []int{}, count: math.MaxInt, want: []int{}}, + {x: []int{0}, count: 0, want: []int{}}, + {x: []int{0}, count: 1, want: []int{0}}, + {x: []int{0}, count: 2, want: []int{0, 0}}, + {x: []int{0}, count: 3, want: []int{0, 0, 0}}, + {x: []int{0}, count: 4, want: []int{0, 0, 0, 0}}, + {x: []int{0, 1}, count: 0, want: []int{}}, + {x: []int{0, 1}, count: 1, want: []int{0, 1}}, + {x: []int{0, 1}, count: 2, want: []int{0, 1, 0, 1}}, + {x: []int{0, 1}, count: 3, want: []int{0, 1, 0, 1, 0, 1}}, + {x: []int{0, 1}, count: 4, want: []int{0, 1, 0, 1, 0, 1, 0, 1}}, + {x: []int{0, 1, 2}, count: 0, want: []int{}}, + {x: []int{0, 1, 2}, count: 1, want: []int{0, 1, 2}}, + {x: []int{0, 1, 2}, count: 2, want: []int{0, 1, 2, 0, 1, 2}}, + {x: []int{0, 1, 2}, count: 3, want: []int{0, 1, 2, 0, 1, 2, 0, 1, 2}}, + {x: []int{0, 1, 2}, count: 4, want: []int{0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2}}, + } { + if got := Repeat(tc.x, tc.count); got == nil || cap(got) != cap(tc.want) || !Equal(got, tc.want) { + t.Errorf("Repeat(%v, %v): got: %v, want: %v, (got == nil): %v, cap(got): %v, cap(want): %v", + tc.x, tc.count, got, tc.want, got == nil, cap(got), cap(tc.want)) + } + } + + // big slices + for _, tc := range []struct { + x []struct{} + count int + want []struct{} + }{ + {x: make([]struct{}, math.MaxInt/1-0), count: 1, want: make([]struct{}, 1*(math.MaxInt/1-0))}, + {x: make([]struct{}, math.MaxInt/2-1), count: 2, want: make([]struct{}, 2*(math.MaxInt/2-1))}, + {x: make([]struct{}, math.MaxInt/3-2), count: 3, want: make([]struct{}, 3*(math.MaxInt/3-2))}, + {x: make([]struct{}, math.MaxInt/4-3), count: 4, want: make([]struct{}, 4*(math.MaxInt/4-3))}, + {x: make([]struct{}, math.MaxInt/5-4), count: 5, want: make([]struct{}, 5*(math.MaxInt/5-4))}, + {x: make([]struct{}, math.MaxInt/6-5), count: 6, want: make([]struct{}, 6*(math.MaxInt/6-5))}, + {x: make([]struct{}, math.MaxInt/7-6), count: 7, want: make([]struct{}, 7*(math.MaxInt/7-6))}, + {x: make([]struct{}, math.MaxInt/8-7), count: 8, want: make([]struct{}, 8*(math.MaxInt/8-7))}, + {x: make([]struct{}, math.MaxInt/9-8), count: 9, want: make([]struct{}, 9*(math.MaxInt/9-8))}, + } { + if got := Repeat(tc.x, tc.count); got == nil || len(got) != len(tc.want) || cap(got) != cap(tc.want) { + t.Errorf("Repeat(make([]struct{}, %v), %v): (got == nil): %v, len(got): %v, len(want): %v, cap(got): %v, cap(want): %v", + len(tc.x), tc.count, got == nil, len(got), len(tc.want), cap(got), cap(tc.want)) + } + } +} + +func TestRepeatPanics(t *testing.T) { + for _, test := range []struct { + name string + x []struct{} + count int + }{ + {name: "cannot be negative", x: make([]struct{}, 0), count: -1}, + {name: "the result of (len(x) * count) overflows, hi > 0", x: make([]struct{}, 3), count: math.MaxInt}, + {name: "the result of (len(x) * count) overflows, lo > maxInt", x: make([]struct{}, 2), count: 1 + math.MaxInt/2}, + } { + if !panics(func() { _ = Repeat(test.x, test.count) }) { + t.Errorf("Repeat %s: got no panic, want panic", test.name) + } + } +}