diff --git a/generic/slice/filter.go b/generic/slice/filter.go index d07795d..b36bea0 100644 --- a/generic/slice/filter.go +++ b/generic/slice/filter.go @@ -4,8 +4,8 @@ package slice type Predicate[T any] func(T) bool // Filter returns a new slice with all elements from the given elems slice for which the Predicate is satisfied. -func Filter[T any](elems []T, predicate Predicate[T]) []T { - var output []T +func Filter[S ~[]T, T any](elems S, predicate Predicate[T]) S { + var output S for _, v := range elems { if predicate(v) { diff --git a/generic/slice/filter_test.go b/generic/slice/filter_test.go index 7e3ca34..38278aa 100644 --- a/generic/slice/filter_test.go +++ b/generic/slice/filter_test.go @@ -12,6 +12,8 @@ func TestFilter(t *testing.T) { Valid bool } + type testStructSlice []testStruct + tests := []struct { name string elems []any @@ -40,9 +42,22 @@ func TestFilter(t *testing.T) { want: []any{testStruct{ID: 1, Valid: true}, testStruct{ID: 3, Valid: true}}, }, } + for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { assert.EqualValues(t, tt.want, Filter(tt.elems, tt.predicate)) }) } + + t.Run("ValidTypedSlice", func(t *testing.T) { + assert.EqualValues(t, testStructSlice{ + testStruct{ID: 1, Valid: true}, + testStruct{ID: 3, Valid: true}, + }, Filter(testStructSlice{ + testStruct{ID: 1, Valid: true}, + testStruct{ID: 2}, + testStruct{ID: 3, Valid: true}, + testStruct{ID: 4}, + }, func(i testStruct) bool { return i.Valid })) + }) } diff --git a/generic/slice/find.go b/generic/slice/find.go index 4b35c2e..501ca3f 100644 --- a/generic/slice/find.go +++ b/generic/slice/find.go @@ -6,7 +6,7 @@ const NotFound = -1 // Find returns the first element from the given elems slice for which the Predicate is satisfied. // Returned can be either the zero value of type or nil(if slice of pointers is given) and the index, // iff found otherwise -1 is returned. -func Find[T any](elems []T, predicate Predicate[T]) (T, int) { +func Find[S ~[]T, T any](elems S, predicate Predicate[T]) (T, int) { for i, v := range elems { if predicate(v) { return v, i diff --git a/generic/slice/find_test.go b/generic/slice/find_test.go index 961a5be..7dcd94c 100644 --- a/generic/slice/find_test.go +++ b/generic/slice/find_test.go @@ -11,6 +11,8 @@ func TestFind(t *testing.T) { ID int } + type testStructSlice []testStruct + tests := []struct { name string elems []any @@ -83,4 +85,28 @@ func TestFind(t *testing.T) { } }) } + + t.Run("TypedSlices", func(t *testing.T) { + t.Run("ElemFound", func(t *testing.T) { + v, index := Find(testStructSlice{ + testStruct{ID: 1}, + testStruct{ID: 2}, + testStruct{ID: 3}, + testStruct{ID: 4}, + }, func(i testStruct) bool { return i.ID == 2 }) + assert.NotEqual(t, NotFound, index) + assert.EqualValues(t, testStruct{ID: 2}, v) + }) + + t.Run("ElemNotFound", func(t *testing.T) { + v, index := Find(testStructSlice{ + testStruct{ID: 1}, + testStruct{ID: 2}, + testStruct{ID: 3}, + testStruct{ID: 4}, + }, func(i testStruct) bool { return i.ID == 5 }) + assert.Equal(t, NotFound, index) + assert.EqualValues(t, testStruct{}, v) + }) + }) } diff --git a/generic/slice/map.go b/generic/slice/map.go index 81abbb8..d6d97a3 100644 --- a/generic/slice/map.go +++ b/generic/slice/map.go @@ -10,7 +10,7 @@ type MapperWithContext[T1, T2 any] func(context.Context, T1) T2 // Map returns a new slice populated with the result of calling the Mapper // on every element in the given elems slice. -func Map[T1, T2 any](elems []T1, mapper Mapper[T1, T2]) []T2 { +func Map[S ~[]T1, T1, T2 any](elems S, mapper Mapper[T1, T2]) []T2 { output := make([]T2, 0, len(elems)) for _, e := range elems { @@ -22,7 +22,8 @@ func Map[T1, T2 any](elems []T1, mapper Mapper[T1, T2]) []T2 { // MapConcurrentWithContext does the same as Map, but concurrently, and receives a context.Context to be cancellable. // Note: For simple map operations, Map is about 50x faster than MapConcurrentWithContext. -func MapConcurrentWithContext[T1, T2 any](ctx context.Context, elems []T1, mapper MapperWithContext[T1, T2]) []T2 { +func MapConcurrentWithContext[S ~[]T1, T1, T2 any]( + ctx context.Context, elems S, mapper MapperWithContext[T1, T2]) []T2 { elemOrder := make(chan chan T2, len(elems)) output := make([]T2, 0, len(elems)) @@ -70,6 +71,6 @@ loop: // MapConcurrent does the same as Map, but concurrently. // Note: For simple map operations, Map is about 50x faster than MapConcurrent. -func MapConcurrent[T1, T2 any](elems []T1, mapper Mapper[T1, T2]) []T2 { +func MapConcurrent[S ~[]T1, T1, T2 any](elems S, mapper Mapper[T1, T2]) []T2 { return MapConcurrentWithContext(context.Background(), elems, func(_ context.Context, e T1) T2 { return mapper(e) }) } diff --git a/generic/slice/map_test.go b/generic/slice/map_test.go index c4eb91e..b47de24 100644 --- a/generic/slice/map_test.go +++ b/generic/slice/map_test.go @@ -12,6 +12,8 @@ type testStructA struct { Value int } +type testStructASlice []testStructA + type testStructB struct { Value int } @@ -46,6 +48,14 @@ func TestMap(t *testing.T) { assert.EqualValues(t, tt.want, Map(tt.elems, tt.mapper)) }) } + + t.Run("MapTypedSlice", func(t *testing.T) { + assert.EqualValues(t, []testStructB{{Value: 4}, {Value: 8}, {Value: 12}}, Map(testStructASlice{ + testStructA{Value: 2}, + testStructA{Value: 4}, + testStructA{Value: 6}, + }, func(i testStructA) testStructB { return testStructB{Value: i.Value * 2} })) + }) } func TestMapConcurrentWithContext(t *testing.T) { diff --git a/generic/slice/reduce.go b/generic/slice/reduce.go index aa06e48..ca7fb14 100644 --- a/generic/slice/reduce.go +++ b/generic/slice/reduce.go @@ -6,7 +6,7 @@ type Accumulator[T1, T2 any] func(T2, T1) T2 // Reduce iterates over the slice and executes an Accumulator function on each element. // The final result of running the Accumulator across all slice elements is returned. -func Reduce[T1, T2 any](elems []T1, accumulator Accumulator[T1, T2]) T2 { +func Reduce[S ~[]T1, T1, T2 any](elems S, accumulator Accumulator[T1, T2]) T2 { var initial T2 return ReduceWithInitialValue(elems, initial, accumulator) @@ -15,7 +15,7 @@ func Reduce[T1, T2 any](elems []T1, accumulator Accumulator[T1, T2]) T2 { // ReduceWithInitialValue iterates over the slice and executes an Accumulator function on each element. // Unlike Reduce, the initial value of the accumulator can be provided as an argument. // The final result of running the Accumulator across all slice elements is returned. -func ReduceWithInitialValue[T1, T2 any](elems []T1, initial T2, accumulator Accumulator[T1, T2]) T2 { +func ReduceWithInitialValue[S ~[]T1, T1, T2 any](elems S, initial T2, accumulator Accumulator[T1, T2]) T2 { for _, v := range elems { initial = accumulator(initial, v) } diff --git a/generic/slice/reduce_test.go b/generic/slice/reduce_test.go index 9528f8e..b16677a 100644 --- a/generic/slice/reduce_test.go +++ b/generic/slice/reduce_test.go @@ -11,6 +11,8 @@ func TestReduce(t *testing.T) { Value float64 } + type testStructSlice []testStruct + tests := []struct { name string elems []any @@ -72,6 +74,23 @@ func TestReduce(t *testing.T) { assert.Equal(t, tt.want, Reduce(tt.elems, tt.accumulator)) }) } + + t.Run("FindStructWithMaxValueFromTypedSlice", func(t *testing.T) { + assert.EqualValues(t, testStruct{Value: 6.91}, Reduce(testStructSlice{ + testStruct{Value: 1.02}, + testStruct{Value: 4.2}, + testStruct{Value: 2.01}, + testStruct{Value: 6.91}, + testStruct{Value: 3.14}, + testStruct{Value: 5.3}, + }, func(in testStruct, e testStruct) testStruct { + if in.Value > e.Value { + return in + } + + return e + })) + }) } func TestReduceWithInitialValue(t *testing.T) { @@ -79,6 +98,8 @@ func TestReduceWithInitialValue(t *testing.T) { Value float64 } + type testStructSlice []testStruct + tests := []struct { name string elems []any @@ -126,4 +147,21 @@ func TestReduceWithInitialValue(t *testing.T) { assert.Equal(t, tt.want, ReduceWithInitialValue(tt.elems, tt.initial, tt.accumulator)) }) } + + t.Run("FindStructWithMaxValueFromTypedSlice", func(t *testing.T) { + assert.EqualValues(t, testStruct{Value: 6.91}, ReduceWithInitialValue(testStructSlice{ + testStruct{Value: 1.02}, + testStruct{Value: 4.2}, + testStruct{Value: 2.01}, + testStruct{Value: 6.91}, + testStruct{Value: 3.14}, + testStruct{Value: 5.3}, + }, testStruct{Value: 0.0}, func(in testStruct, e testStruct) testStruct { + if in.Value > e.Value { + return in + } + + return e + })) + }) }