Skip to content

Commit 5db534f

Browse files
authored
Merge pull request #3 from thecasualcoder/reduce
Add reduce function
2 parents 48e4ef7 + 8cd0d36 commit 5db534f

File tree

3 files changed

+294
-0
lines changed

3 files changed

+294
-0
lines changed

README.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ This library heavily makes use of `reflect` package and hence will have an **imp
1111

1212
1. [Map](#Map)
1313
2. [Filter](#Filter)
14+
3. [Reduce](#Reduce)
1415

1516
## Usages
1617

@@ -88,3 +89,45 @@ func main() {
8889
fmt.Println(output) // prints {Doe 30}
8990
}
9091
```
92+
93+
### Reduce
94+
95+
Reduce reduces the given collection using given reduce function
96+
97+
_Primitive types_
98+
99+
```go
100+
func main() {
101+
input := []int{1, 2, 3, 4, 5}
102+
var output int
103+
104+
godash.Reduce(input, &output, func(sum, element int) int {
105+
return sum + element
106+
})
107+
108+
fmt.Println(output) // prints 15
109+
}
110+
```
111+
112+
_Struct type_
113+
114+
```go
115+
type Person struct {
116+
Name string
117+
Age Int
118+
}
119+
120+
func main() {
121+
input := []Person{
122+
{Name: "John", Age: 22},
123+
{Name: "Doe", Age: 23},
124+
}
125+
var output int
126+
127+
godash.Reduce(input, &output, func(sum int, person Person) int {
128+
return sum + person.Age
129+
})
130+
131+
fmt.Println(output) // prints 45
132+
}
133+
```

reduce.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package godash
2+
3+
import (
4+
"fmt"
5+
"reflect"
6+
)
7+
8+
// Reduce reduces the given collection using given reduce function
9+
func Reduce(in, out, reduceFn interface{}) error {
10+
input := reflect.ValueOf(in)
11+
output := reflect.ValueOf(out)
12+
if err := isReferenceType(output); err != nil {
13+
return err
14+
}
15+
16+
reducer := reflect.ValueOf(reduceFn)
17+
if err := validateReducer(reducer); err != nil {
18+
return err
19+
}
20+
21+
if input.Kind() == reflect.Slice {
22+
outputKind := output.Elem().Kind()
23+
reducerFnType := reducer.Type()
24+
if outputKind != reducerFnType.In(0).Kind() {
25+
return fmt.Errorf("reduceFn's first argument's type(%s) has to be the type of out(%s)", reducerFnType.In(0).Kind(), outputKind)
26+
}
27+
if input.Type().Elem().Kind() != reducerFnType.In(1).Kind() {
28+
return fmt.Errorf("reduceFn's second argument's type(%s) has to be the type of element of input slice(%s)", reducerFnType.In(1).Kind(), input.Type().Elem().Kind())
29+
}
30+
if outputKind != reducerFnType.Out(0).Kind() {
31+
return fmt.Errorf("reduceFn's return type(%s) has to be the type of out(%s)", reducerFnType.Out(0).Kind(), outputKind)
32+
}
33+
34+
result := output.Elem()
35+
for i := 0; i < input.Len(); i++ {
36+
arg := input.Index(i)
37+
returnValues := reducer.Call([]reflect.Value{result, arg})
38+
39+
result = returnValues[0]
40+
}
41+
output.Elem().Set(result)
42+
43+
return nil
44+
}
45+
return fmt.Errorf("not implemented")
46+
}
47+
48+
func validateReducer(reducer reflect.Value) error {
49+
reducerFnType := reducer.Type()
50+
if reducer.Kind() != reflect.Func {
51+
return fmt.Errorf("reduceFn has to be a (func) and not (%s)", reducer.Kind())
52+
}
53+
if reducerFnType.NumIn() != 2 {
54+
return fmt.Errorf("reduceFn has to take exactly 2 arguments and not %d argument(s)", reducerFnType.NumIn())
55+
}
56+
if reducerFnType.NumOut() != 1 {
57+
return fmt.Errorf("reduceFn should have only one return value and not %d return type(s)", reducerFnType.NumOut())
58+
}
59+
return nil
60+
}
61+
62+
func isReferenceType(output reflect.Value) error {
63+
zeroValue := reflect.Value{}
64+
if output == zeroValue {
65+
return fmt.Errorf("output is nil. Pass a reference to set output")
66+
}
67+
if output.Kind() != reflect.Ptr {
68+
return fmt.Errorf("cannot set out. Pass a reference to set output")
69+
}
70+
return nil
71+
}

reduce_test.go

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
package godash_test
2+
3+
import (
4+
"github.com/stretchr/testify/assert"
5+
"github.com/thecasualcoder/godash"
6+
"strconv"
7+
"testing"
8+
)
9+
10+
func TestReduce(t *testing.T) {
11+
t.Run("support primitive types", func(t *testing.T) {
12+
{
13+
in := []int{1, 2, 3}
14+
var out int
15+
16+
err := godash.Reduce(in, &out, func(acc, element int) int {
17+
return acc + element
18+
})
19+
20+
expected := 6
21+
assert.NoError(t, err)
22+
assert.Equal(t, expected, out)
23+
}
24+
{
25+
in := []int{1, 2, 3}
26+
var out string
27+
28+
err := godash.Reduce(in, &out, func(acc string, element int) string {
29+
return acc + strconv.Itoa(element)
30+
})
31+
32+
expected := "123"
33+
assert.NoError(t, err)
34+
assert.Equal(t, expected, out)
35+
}
36+
{
37+
in := []string{"one", "two", "two", "three", "three", "three"}
38+
out := map[string]int{}
39+
40+
err := godash.Reduce(in, &out, func(acc map[string]int, element string) map[string]int {
41+
if _, present := acc[element]; present {
42+
acc[element] = acc[element] + 1
43+
} else {
44+
acc[element] = 1
45+
}
46+
return acc
47+
})
48+
49+
expected := map[string]int{"one": 1, "two": 2, "three": 3}
50+
assert.NoError(t, err)
51+
assert.Equal(t, expected, out)
52+
}
53+
})
54+
55+
t.Run("support structs", func(t *testing.T) {
56+
type person struct {
57+
name string
58+
age int
59+
}
60+
61+
in := []person{
62+
{name: "john", age: 20},
63+
{name: "doe", age: 23},
64+
}
65+
out := 0
66+
expected := 43
67+
68+
err := godash.Reduce(in, &out, func(acc int, p person) int {
69+
return acc + p.age
70+
})
71+
72+
assert.NoError(t, err)
73+
assert.Equal(t, expected, out)
74+
})
75+
76+
add := func(acc, element int) int {
77+
return acc + element
78+
}
79+
80+
t.Run("should not panic if output is nil", func(t *testing.T) {
81+
in := []int{1, 2, 3}
82+
{
83+
var out int
84+
85+
err := godash.Reduce(in, out, add)
86+
87+
assert.EqualError(t, err, "cannot set out. Pass a reference to set output")
88+
}
89+
90+
{
91+
err := godash.Reduce(in, nil, add)
92+
93+
assert.EqualError(t, err, "output is nil. Pass a reference to set output")
94+
}
95+
})
96+
97+
t.Run("should not accept reducer function that are not functions", func(t *testing.T) {
98+
in := []int{1, 2, 3}
99+
var out int
100+
101+
err := godash.Reduce(in, &out, 7)
102+
103+
assert.EqualError(t, err, "reduceFn has to be a (func) and not (int)")
104+
})
105+
106+
t.Run("should not accept reducer function that do not take exactly two argument", func(t *testing.T) {
107+
in := []int{1, 2, 3}
108+
var out int
109+
110+
{
111+
err := godash.Reduce(in, &out, func() int { return 0 })
112+
assert.EqualError(t, err, "reduceFn has to take exactly 2 arguments and not 0 argument(s)")
113+
}
114+
115+
{
116+
err := godash.Reduce(in, &out, func(int) int { return 0 })
117+
assert.EqualError(t, err, "reduceFn has to take exactly 2 arguments and not 1 argument(s)")
118+
}
119+
})
120+
121+
t.Run("should not accept reducer function that do not return exactly one value", func(t *testing.T) {
122+
in := []int{1, 2, 3}
123+
var out int
124+
125+
{
126+
err := godash.Reduce(in, &out, func(int, int) {})
127+
assert.EqualError(t, err, "reduceFn should have only one return value and not 0 return type(s)")
128+
}
129+
130+
{
131+
err := godash.Reduce(in, &out, func(int, int) (int, int) { return 0, 0 })
132+
assert.EqualError(t, err, "reduceFn should have only one return value and not 2 return type(s)")
133+
}
134+
})
135+
136+
t.Run("should accept reducer function whose first argument's kind should be output's kind", func(t *testing.T) {
137+
in := []int{1, 2, 3}
138+
var out int
139+
140+
{
141+
err := godash.Reduce(in, &out, func(string, int) int { return 0 })
142+
assert.EqualError(t, err, "reduceFn's first argument's type(string) has to be the type of out(int)")
143+
}
144+
145+
{
146+
err := godash.Reduce(in, &out, func(int, int) int { return 0 })
147+
assert.NoError(t, err)
148+
}
149+
})
150+
151+
t.Run("should accept reducer function whose second argument's kind should be input slice's element kind", func(t *testing.T) {
152+
in := []int{1, 2, 3}
153+
var out string
154+
155+
{
156+
err := godash.Reduce(in, &out, func(string, string) string { return "" })
157+
assert.EqualError(t, err, "reduceFn's second argument's type(string) has to be the type of element of input slice(int)")
158+
}
159+
160+
{
161+
err := godash.Reduce(in, &out, func(string, int) string { return "" })
162+
assert.NoError(t, err)
163+
}
164+
})
165+
166+
t.Run("should accept reducer function whose return kind should be output's kind", func(t *testing.T) {
167+
in := []int{1, 2, 3}
168+
var out string
169+
170+
{
171+
err := godash.Reduce(in, &out, func(string, int) int { return 0 })
172+
assert.EqualError(t, err, "reduceFn's return type(int) has to be the type of out(string)")
173+
}
174+
175+
{
176+
err := godash.Reduce(in, &out, func(string, int) string { return "" })
177+
assert.NoError(t, err)
178+
}
179+
})
180+
}

0 commit comments

Comments
 (0)