Skip to content

Commit

Permalink
add support for accepting typed slices
Browse files Browse the repository at this point in the history
  • Loading branch information
ajatprabha committed Feb 16, 2023
1 parent 3c9c777 commit a5bd4a7
Show file tree
Hide file tree
Showing 8 changed files with 98 additions and 8 deletions.
4 changes: 2 additions & 2 deletions generic/slice/filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
15 changes: 15 additions & 0 deletions generic/slice/filter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ func TestFilter(t *testing.T) {
Valid bool
}

type testStructSlice []testStruct

tests := []struct {
name string
elems []any
Expand Down Expand Up @@ -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 }))
})
}
2 changes: 1 addition & 1 deletion generic/slice/find.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
26 changes: 26 additions & 0 deletions generic/slice/find_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ func TestFind(t *testing.T) {
ID int
}

type testStructSlice []testStruct

tests := []struct {
name string
elems []any
Expand Down Expand Up @@ -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)
})
})
}
7 changes: 4 additions & 3 deletions generic/slice/map.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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))

Expand Down Expand Up @@ -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) })
}
10 changes: 10 additions & 0 deletions generic/slice/map_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ type testStructA struct {
Value int
}

type testStructASlice []testStructA

type testStructB struct {
Value int
}
Expand Down Expand Up @@ -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) {
Expand Down
4 changes: 2 additions & 2 deletions generic/slice/reduce.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
}
Expand Down
38 changes: 38 additions & 0 deletions generic/slice/reduce_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ func TestReduce(t *testing.T) {
Value float64
}

type testStructSlice []testStruct

tests := []struct {
name string
elems []any
Expand Down Expand Up @@ -72,13 +74,32 @@ 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) {
type testStruct struct {
Value float64
}

type testStructSlice []testStruct

tests := []struct {
name string
elems []any
Expand Down Expand Up @@ -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
}))
})
}

0 comments on commit a5bd4a7

Please sign in to comment.