From 6f703fe577d6835a41cf0daff6fc213ef1b0168e Mon Sep 17 00:00:00 2001 From: Idichekop Date: Mon, 4 Aug 2025 04:45:29 +0200 Subject: [PATCH] fix(package): [slice] Fix RigthPadding and LeftPadding (#322) * Fixes in RightPadding and LeftPadding * Tests cover padding empty, nil, and negative lenght * Implemented repeat, concat, and grow functionalities as internal. --- slice/slice.go | 36 +++++++++------------ slice/slice_internal.go | 69 +++++++++++++++++++++++++++++++++++++++++ slice/slice_test.go | 14 +++++++++ 3 files changed, 97 insertions(+), 22 deletions(-) diff --git a/slice/slice.go b/slice/slice.go index 79ebfee..c477989 100644 --- a/slice/slice.go +++ b/slice/slice.go @@ -1463,36 +1463,28 @@ func Random[T any](slice []T) (val T, idx int) { return slice[idx], idx } -// RightPadding adds padding to the right end of a slice. +// RightPadding returns a copy of the slice padding the given value to the right end of a slice. +// If paddingLength is zero or less, the function returns a copy of the slice. // Play: https://go.dev/play/p/0_2rlLEMBXL func RightPadding[T any](slice []T, paddingValue T, paddingLength int) []T { - if paddingLength == 0 { - return slice + suffix := []T{} + if paddingLength > 0 { + suffix = repeat([]T{paddingValue}, paddingLength) } - for i := 0; i < paddingLength; i++ { - slice = append(slice, paddingValue) - } - return slice + padded := concat(slice, suffix) + return padded } -// LeftPadding adds padding to the left begin of a slice. +// LeftPadding returns a copy of the slice padding the given value to the left begin of a slice. +// If paddingLength is zero or less, the function returns a copy of the slice. // Play: https://go.dev/play/p/jlQVoelLl2k func LeftPadding[T any](slice []T, paddingValue T, paddingLength int) []T { - if paddingLength == 0 { - return slice + prefix := []T{} + if paddingLength > 0 { + prefix = repeat([]T{paddingValue}, paddingLength) } - - paddedSlice := make([]T, len(slice)+paddingLength) - i := 0 - for ; i < paddingLength; i++ { - paddedSlice[i] = paddingValue - } - for j := 0; j < len(slice); j++ { - paddedSlice[i] = slice[j] - i++ - } - - return paddedSlice + padded := concat(prefix, slice) + return padded } // Frequency counts the frequency of each element in the slice. diff --git a/slice/slice_internal.go b/slice/slice_internal.go index f6c275e..201fa53 100644 --- a/slice/slice_internal.go +++ b/slice/slice_internal.go @@ -2,6 +2,7 @@ package slice import ( "fmt" + "math/bits" "reflect" "golang.org/x/exp/constraints" @@ -96,3 +97,71 @@ func partitionAnySlice[T any](slice []T, lowIndex, highIndex int, less func(a, b func swap[T any](slice []T, i, j int) { slice[i], slice[j] = slice[j], slice[i] } + +// `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. +// +// repeat has been provided in the standard lib within the package `slices` under the +// name Repeat since GO version 1.21 onwards. As lancet commits to compatibility with GO +// 1.18 onwards, we implement the functionality as an internal function. +func repeat[S ~[]E, E any](x S, count int) S { + if count < 0 { + panic("count cannot be negative") + } + + const maxInt = ^uint(0) >> 1 + hi, lo := bits.Mul(uint(len(x)), uint(count)) + if hi > 0 || lo > maxInt { + panic("the result of (len(x) * count) overflows") + } + + newslice := make(S, int(lo)) // lo = len(x) * count + n := copy(newslice, x) + for n < len(newslice) { + n += copy(newslice[n:], newslice[:n]) + } + return newslice +} + +// concat returns a new slice concatenating the passed in slices. +// +// concat has been provided in the standard lib within the package `slices` under the +// name Concat since GO version 1.21 onwards. As lancet commits to compatibility with GO +// 1.18 onwards, we implement the functionality as an internal function. +func concat[S ~[]E, E any](slices ...S) S { + size := 0 + for _, s := range slices { + size += len(s) + if size < 0 { + panic("len out of range") + } + } + // Use Grow, not make, to round up to the size class: + // the extra space is otherwise unused and helps + // callers that append a few elements to the result. + newslice := grow[S](nil, size) + for _, s := range slices { + newslice = append(newslice, s...) + } + return newslice +} + +// grow increases the slice's capacity, if necessary, to guarantee space for +// another n elements. After grow(n), at least n elements can be appended +// to the slice without another allocation. If n is negative or too large to +// allocate the memory, grow panics. +// +// grow has been provided in the standard lib within the package `slices` under the +// name Grow since GO version 1.21 onwards. As lancet commits to compatibility with GO +// 1.18 onwards, we implement the functionality as an internal function. +func grow[S ~[]E, E any](s S, n int) S { + if n < 0 { + panic("cannot be negative") + } + if n -= cap(s) - len(s); n > 0 { + // This expression allocates only once. + s = append(s[:cap(s)], make([]E, n)...)[:len(s)] + } + return s +} diff --git a/slice/slice_test.go b/slice/slice_test.go index fbbed93..8fa8976 100644 --- a/slice/slice_test.go +++ b/slice/slice_test.go @@ -1756,6 +1756,20 @@ func TestRightPaddingAndLeftPadding(t *testing.T) { padded := LeftPadding(RightPadding(nums, 0, 3), 0, 3) assert.Equal([]int{0, 0, 0, 1, 2, 3, 4, 5, 0, 0, 0}, padded) + + // Test with negative padding length + paddedNegative := LeftPadding(RightPadding(nums, 0, -3), 0, -3) + assert.Equal([]int{1, 2, 3, 4, 5}, paddedNegative) + + // Test with empty slice + empty := []int{} + paddedEmpty := LeftPadding(RightPadding(empty, 0, 3), 0, 3) + assert.Equal([]int{0, 0, 0, 0, 0, 0}, paddedEmpty) + + // Test with nil + nilSlice := []int(nil) + paddedNil := LeftPadding(RightPadding(nilSlice, 0, 3), 0, 3) + assert.Equal([]int{0, 0, 0, 0, 0, 0}, paddedNil) } func TestUniqueByConcurrent(t *testing.T) {