Skip to content

Commit d873409

Browse files
committed
refactor: introduce clone helper for slice copying
- Add clone() function that returns a shallow copy without preserving nilness is guaranteed to be inlined by the compiler (verified with -gcflags=-m) (separate from Clone() which preserves nilness - library convention is to return empty slice) - Simplify Clone() to delegate to clone() for non-nil collections - Refactor Chunk, Sliding, Drop, DropRight, DropWhile, DropRightWhile, Take, TakeWhile, Replace, Trim to use clone() - Update SamplesBy and debounce.reset to use clone() - Replace make(Slice, 0) with Slice{} for return statements Also - Some redundant checks have been removed in Take, NthOrEmpty - Simplify TrimPrefix and TrimSuffix loops - Micro-optimize Replace() by reordering condition to short-circuit on n != 0 first (TODO: consider adding early break when n reaches 0)
1 parent d610980 commit d873409

4 files changed

Lines changed: 40 additions & 61 deletions

File tree

find.go

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -671,11 +671,7 @@ func NthOr[T any, N constraints.Integer](collection []T, nth N, fallback T) T {
671671
// If `nth` is out of slice bounds, it returns the zero value (empty value) for that type.
672672
// Play: https://go.dev/play/p/sHoh88KWt6B
673673
func NthOrEmpty[T any, N constraints.Integer](collection []T, nth N) T {
674-
value, err := Nth(collection, nth)
675-
if err != nil {
676-
var zeroValue T
677-
return zeroValue
678-
}
674+
value, _ := Nth(collection, nth)
679675
return value
680676
}
681677

@@ -686,8 +682,7 @@ type randomIntGenerator func(n int) int
686682
// Sample returns a random item from collection.
687683
// Play: https://go.dev/play/p/vCcSJbh5s6l
688684
func Sample[T any](collection []T) T {
689-
result := SampleBy(collection, xrand.IntN)
690-
return result
685+
return SampleBy(collection, xrand.IntN)
691686
}
692687

693688
// SampleBy returns a random item from collection, using randomIntGenerator as the random index generator.
@@ -715,7 +710,7 @@ func SamplesBy[T any, Slice ~[]T](collection Slice, count int, randomIntGenerato
715710
count = size
716711
}
717712

718-
cOpy := append(Slice{}, collection...)
713+
cOpy := clone(collection)
719714

720715
results := make(Slice, count)
721716

retry.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ func (d *debounce) reset() {
3030
d.timer = time.AfterFunc(d.after, func() {
3131
// We need to lock the mutex here to avoid race conditions with 2 concurrent calls to reset()
3232
d.mu.Lock()
33-
callbacks := append([]func(){}, d.callbacks...)
33+
callbacks := clone(d.callbacks)
3434
d.mu.Unlock()
3535

3636
for i := range callbacks {

slice.go

Lines changed: 30 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -224,9 +224,7 @@ func Chunk[T any, Slice ~[]T](collection Slice, size int) []Slice {
224224
}
225225

226226
// Copy chunk in a new slice, to prevent memory leak and free memory from initial collection.
227-
newSlice := make(Slice, last-i*size)
228-
copy(newSlice, collection[i*size:last])
229-
result = append(result, newSlice)
227+
result = append(result, clone(collection[i*size:last]))
230228
}
231229

232230
return result
@@ -312,9 +310,7 @@ func Sliding[T any, Slice ~[]T](collection Slice, size, step int) []Slice {
312310
result := make([]Slice, 0, n/step+1)
313311

314312
for i := 0; i <= n; i += step {
315-
window := make(Slice, size)
316-
copy(window, collection[i:i+size])
317-
result = append(result, window)
313+
result = append(result, clone(collection[i:i+size]))
318314
}
319315

320316
return result
@@ -517,12 +513,10 @@ func Drop[T any, Slice ~[]T](collection Slice, n int) Slice {
517513
}
518514

519515
if len(collection) <= n {
520-
return make(Slice, 0)
516+
return Slice{}
521517
}
522518

523-
result := make(Slice, 0, len(collection)-n)
524-
525-
return append(result, collection[n:]...)
519+
return clone(collection[n:])
526520
}
527521

528522
// DropRight drops n elements from the end of a slice.
@@ -536,8 +530,7 @@ func DropRight[T any, Slice ~[]T](collection Slice, n int) Slice {
536530
return Slice{}
537531
}
538532

539-
result := make(Slice, 0, len(collection)-n)
540-
return append(result, collection[:len(collection)-n]...)
533+
return clone(collection[:len(collection)-n])
541534
}
542535

543536
// DropWhile drops elements from the beginning of a slice while the predicate returns true.
@@ -550,8 +543,7 @@ func DropWhile[T any, Slice ~[]T](collection Slice, predicate func(item T) bool)
550543
}
551544
}
552545

553-
result := make(Slice, 0, len(collection)-i)
554-
return append(result, collection[i:]...)
546+
return clone(collection[i:])
555547
}
556548

557549
// DropRightWhile drops elements from the end of a slice while the predicate returns true.
@@ -564,8 +556,7 @@ func DropRightWhile[T any, Slice ~[]T](collection Slice, predicate func(item T)
564556
}
565557
}
566558

567-
result := make(Slice, 0, i+1)
568-
return append(result, collection[:i+1]...)
559+
return clone(collection[:i+1])
569560
}
570561

571562
// Take takes the first n elements from a slice.
@@ -574,24 +565,11 @@ func Take[T any, Slice ~[]T](collection Slice, n int) Slice {
574565
panic("lo.Take: n must not be negative")
575566
}
576567

577-
if n == 0 {
578-
return make(Slice, 0)
579-
}
580-
581-
size := len(collection)
582-
if size == 0 {
583-
return make(Slice, 0)
568+
if len(collection) > n {
569+
collection = collection[:n]
584570
}
585571

586-
if n >= size {
587-
result := make(Slice, size)
588-
copy(result, collection)
589-
return result
590-
}
591-
592-
result := make(Slice, n)
593-
copy(result, collection)
594-
return result
572+
return clone(collection)
595573
}
596574

597575
// TakeWhile takes elements from the beginning of a slice while the predicate returns true.
@@ -603,9 +581,7 @@ func TakeWhile[T any, Slice ~[]T](collection Slice, predicate func(item T) bool)
603581
}
604582
}
605583

606-
result := make(Slice, i)
607-
copy(result, collection[:i])
608-
return result
584+
return clone(collection[:i])
609585
}
610586

611587
// DropByIndex drops elements from a slice by the index.
@@ -614,7 +590,7 @@ func TakeWhile[T any, Slice ~[]T](collection Slice, predicate func(item T) bool)
614590
func DropByIndex[T any, Slice ~[]T](collection Slice, indexes ...int) Slice {
615591
initialSize := len(collection)
616592
if initialSize == 0 {
617-
return make(Slice, 0)
593+
return Slice{}
618594
}
619595

620596
for i := range indexes {
@@ -648,7 +624,7 @@ func TakeFilter[T any, Slice ~[]T](collection Slice, n int, predicate func(item
648624
}
649625

650626
if n == 0 {
651-
return make(Slice, 0)
627+
return Slice{}
652628
}
653629

654630
result := make(Slice, 0, n)
@@ -822,11 +798,10 @@ func Slice[T any, Slice ~[]T](collection Slice, start, end int) Slice {
822798
// Replace returns a copy of the slice with the first n non-overlapping instances of old replaced by new.
823799
// Play: https://go.dev/play/p/XfPzmf9gql6
824800
func Replace[T comparable, Slice ~[]T](collection Slice, old, nEw T, n int) Slice {
825-
result := make(Slice, len(collection))
826-
copy(result, collection)
801+
result := clone(collection)
827802

828803
for i := range result {
829-
if result[i] == old && n != 0 {
804+
if n != 0 && result[i] == old {
830805
result[i] = nEw
831806
n--
832807
}
@@ -850,6 +825,12 @@ func Clone[T any, Slice ~[]T](collection Slice) Slice {
850825
if collection == nil {
851826
return nil
852827
}
828+
829+
return clone(collection)
830+
}
831+
832+
// clone returns a shallow copy of the collection without preserving nilness.
833+
func clone[T any, Slice ~[]T](collection Slice) Slice {
853834
// Avoid s[:0:0] as it leads to unwanted liveness when cloning a
854835
// zero-length slice of a large array; see https://go.dev/issue/68488.
855836
return append(Slice{}, collection...)
@@ -934,7 +915,7 @@ func Splice[T any, Slice ~[]T](collection Slice, i int, elements ...T) Slice {
934915
// Play: https://go.dev/play/p/GiL3qhpIP3f
935916
func Cut[T comparable, Slice ~[]T](collection, separator Slice) (before, after Slice, found bool) {
936917
if len(separator) == 0 {
937-
return make(Slice, 0), collection, true
918+
return Slice{}, collection, true
938919
}
939920

940921
for i := 0; i+len(separator) <= len(collection); i++ {
@@ -950,7 +931,7 @@ func Cut[T comparable, Slice ~[]T](collection, separator Slice) (before, after S
950931
}
951932
}
952933

953-
return collection, make(Slice, 0), false
934+
return collection, Slice{}, false
954935
}
955936

956937
// CutPrefix returns collection without the provided leading prefix []T
@@ -1020,8 +1001,7 @@ func Trim[T comparable, Slice ~[]T](collection, cutset Slice) Slice {
10201001
}
10211002
}
10221003

1023-
result := make(Slice, 0, j+1-i)
1024-
return append(result, collection[i:j+1]...)
1004+
return clone(collection[i : j+1])
10251005
}
10261006

10271007
// TrimLeft removes all the leading cutset from the collection.
@@ -1042,12 +1022,11 @@ func TrimPrefix[T comparable, Slice ~[]T](collection, prefix Slice) Slice {
10421022
return collection
10431023
}
10441024

1045-
for {
1046-
if !HasPrefix(collection, prefix) {
1047-
return collection
1048-
}
1025+
for HasPrefix(collection, prefix) {
10491026
collection = collection[len(prefix):]
10501027
}
1028+
1029+
return collection
10511030
}
10521031

10531032
// TrimRight removes all the trailing cutset from the collection.
@@ -1068,10 +1047,9 @@ func TrimSuffix[T comparable, Slice ~[]T](collection, suffix Slice) Slice {
10681047
return collection
10691048
}
10701049

1071-
for {
1072-
if !HasSuffix(collection, suffix) {
1073-
return collection
1074-
}
1050+
for HasSuffix(collection, suffix) {
10751051
collection = collection[:len(collection)-len(suffix)]
10761052
}
1053+
1054+
return collection
10771055
}

slice_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1525,6 +1525,12 @@ func TestCutSuccess(t *testing.T) {
15251525
is.True(result)
15261526
is.Equal([]string{}, actualLeft)
15271527
is.Equal([]string{"b"}, actualRight)
1528+
1529+
// case 8
1530+
actualLeft, actualRight, result = Cut([]string{"a", "b"}, []string{})
1531+
is.True(result)
1532+
is.Equal([]string{}, actualLeft)
1533+
is.Equal([]string{"a", "b"}, actualRight)
15281534
}
15291535

15301536
func TestCutFail(t *testing.T) {

0 commit comments

Comments
 (0)