feat: adding MeanByErr helper

This commit is contained in:
Samuel Berthe
2026-02-28 17:30:54 +01:00
parent b0b34ffc94
commit 153f867680
5 changed files with 179 additions and 1 deletions
+12
View File
@@ -2119,6 +2119,18 @@ mean := lo.MeanBy([]float64{}, mapper)
// 0
```
```go
// Use MeanByErr when the transform function can return an error
list := []string{"aa", "bbb", "cccc", "ddddd"}
mean, err := lo.MeanByErr(list, func(item string) (float64, error) {
if item == "cccc" {
return 0, fmt.Errorf("cccc is not allowed")
}
return float64(len(item)), nil
})
// 0, error("cccc is not allowed")
```
[[play](https://go.dev/play/p/j7TsVwBOZ7P)]
### Mode
+2 -1
View File
@@ -1,7 +1,7 @@
---
name: MeanBy
slug: meanby
sourceRef: math.go#L137
sourceRef: math.go#L161
category: core
subCategory: math
playUrl: https://go.dev/play/p/j7TsVwBOZ7P
@@ -9,6 +9,7 @@ variantHelpers:
- core#math#meanby
similarHelpers:
- core#math#mean
- core#math#meanbyerr
- core#math#mode
- core#math#sum
- core#math#sumby
+49
View File
@@ -0,0 +1,49 @@
---
name: MeanByErr
slug: meanbyerr
sourceRef: math.go#L172
category: core
subCategory: math
variantHelpers:
- core#math#meanbyerr
similarHelpers:
- core#math#meanby
- core#math#mean
- core#math#mode
- core#math#sum
- core#math#sumbyerr
- core#math#product
- core#math#productby
- core#find#min
- core#find#max
- core#find#minby
- core#find#maxby
position: 91
signatures:
- "func MeanByErr[T any, R constraints.Float | constraints.Integer](collection []T, iteratee func(item T) (R, error)) (R, error)"
---
Calculates the mean of values computed by a predicate. Returns 0 for an empty collection.
If the iteratee returns an error, iteration stops and the error is returned.
```go
list := []string{"aa", "bbb", "cccc", "ddddd"}
result, err := lo.MeanByErr(list, func(item string) (float64, error) {
return float64(len(item)), nil
})
// 3.5, <nil>
```
Example with error:
```go
list := []string{"aa", "bbb", "cccc", "ddddd"}
result, err := lo.MeanByErr(list, func(item string) (float64, error) {
if item == "cccc" {
return 0, fmt.Errorf("cccc is not allowed")
}
return float64(len(item)), nil
})
// 0, error("cccc is not allowed")
```
+15
View File
@@ -169,6 +169,21 @@ func MeanBy[T any, R constraints.Float | constraints.Integer](collection []T, it
return sum / length
}
// MeanByErr calculates the mean of a collection of numbers using the given return value from the iteration function.
// If the iteratee returns an error, iteration stops and the error is returned.
// If collection is empty 0 and nil error are returned.
func MeanByErr[T any, R constraints.Float | constraints.Integer](collection []T, iteratee func(item T) (R, error)) (R, error) {
length := R(len(collection))
if length == 0 {
return 0, nil
}
sum, err := SumByErr(collection, iteratee)
if err != nil {
return 0, err
}
return sum / length, nil
}
// Mode returns the mode (most frequent value) of a collection.
// If multiple values have the same highest frequency, then multiple values are returned.
// If the collection is empty, then the zero value of T is returned.
+101
View File
@@ -386,6 +386,107 @@ func TestMeanBy(t *testing.T) {
is.Equal(uint32(0), result4)
}
func TestMeanByErr(t *testing.T) {
t.Parallel()
is := assert.New(t)
testErr := assert.AnError
// Test normal operation (no error) - table driven
tests := []struct {
name string
input interface{}
expected interface{}
}{
{
name: "float32 slice",
input: []float32{2.3, 3.3, 4, 5.3},
expected: float32(3.725),
},
{
name: "int32 slice",
input: []int32{2, 3, 4, 5},
expected: int32(3),
},
{
name: "uint32 slice",
input: []uint32{2, 3, 4, 5},
expected: uint32(3),
},
{
name: "empty uint32 slice",
input: []uint32{},
expected: uint32(0),
},
{
name: "nil int32 slice",
input: ([]int32)(nil),
expected: int32(0),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
switch input := tt.input.(type) {
case []float32:
result, err := MeanByErr(input, func(n float32) (float32, error) { return n, nil })
is.NoError(err)
is.InEpsilon(tt.expected.(float32), result, 1e-7)
case []int32:
result, err := MeanByErr(input, func(n int32) (int32, error) { return n, nil })
is.NoError(err)
is.Equal(tt.expected.(int32), result)
case []uint32:
result, err := MeanByErr(input, func(n uint32) (uint32, error) { return n, nil })
is.NoError(err)
is.Equal(tt.expected.(uint32), result)
}
})
}
// Test error cases - table driven
errorTests := []struct {
name string
input []int32
errorAt int32
expectedCalls int
}{
{
name: "error at third element",
input: []int32{1, 2, 3, 4, 5},
errorAt: 3,
expectedCalls: 3,
},
{
name: "error at first element",
input: []int32{1, 2, 3},
errorAt: 1,
expectedCalls: 1,
},
{
name: "error at last element",
input: []int32{1, 2, 3},
errorAt: 3,
expectedCalls: 3,
},
}
for _, tt := range errorTests {
t.Run(tt.name, func(t *testing.T) {
callbackCount := 0
_, err := MeanByErr(tt.input, func(n int32) (int32, error) {
callbackCount++
if n == tt.errorAt {
return 0, testErr
}
return n, nil
})
is.ErrorIs(err, testErr)
is.Equal(tt.expectedCalls, callbackCount)
})
}
}
func TestMode(t *testing.T) {
t.Parallel()
is := assert.New(t)