Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add support for accepting typed slices #4

Merged
merged 1 commit into from
Feb 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
}))
})
}