Skip to content

Commit 01cfa66

Browse files
committed
Add reduce function
Signed-off-by: Dinesh <dineshudt17@gmail.com>
1 parent a401794 commit 01cfa66

File tree

2 files changed

+234
-0
lines changed

2 files changed

+234
-0
lines changed

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("reducer function's first argument has to be the type of out")
26+
}
27+
if input.Type().Elem().Kind() != reducerFnType.In(1).Kind() {
28+
return fmt.Errorf("reducer function's second argument has to be the type of element of input slice")
29+
}
30+
if outputKind != reducerFnType.Out(0).Kind() {
31+
return fmt.Errorf("reducer function's return type has to be the type of out")
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 function")
52+
}
53+
if reducerFnType.NumIn() != 2 {
54+
return fmt.Errorf("reducer function has to take exactly two argument")
55+
}
56+
if reducerFnType.NumOut() != 1 {
57+
return fmt.Errorf("reducer function should return only one return value")
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: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
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+
38+
t.Run("support structs", func(t *testing.T) {
39+
type person struct {
40+
name string
41+
age int
42+
}
43+
44+
in := []person{
45+
{name: "john", age: 20},
46+
{name: "doe", age: 23},
47+
}
48+
out := 0
49+
expected := 43
50+
51+
err := godash.Reduce(in, &out, func(acc int, p person) int {
52+
return acc + p.age
53+
})
54+
55+
assert.NoError(t, err)
56+
assert.Equal(t, expected, out)
57+
})
58+
59+
add := func(acc, element int) int {
60+
return acc + element
61+
}
62+
63+
t.Run("should not panic if output is nil", func(t *testing.T) {
64+
in := []int{1, 2, 3}
65+
{
66+
var out int
67+
68+
err := godash.Reduce(in, out, add)
69+
70+
assert.EqualError(t, err, "cannot set out. Pass a reference to set output")
71+
}
72+
73+
{
74+
err := godash.Reduce(in, nil, add)
75+
76+
assert.EqualError(t, err, "output is nil. Pass a reference to set output")
77+
}
78+
})
79+
80+
t.Run("should not accept reducer function that are not functions", func(t *testing.T) {
81+
in := []int{1, 2, 3}
82+
var out int
83+
84+
err := godash.Reduce(in, &out, 7)
85+
86+
assert.EqualError(t, err, "reduceFn has to be a function")
87+
})
88+
89+
t.Run("should not accept reducer function that do not take exactly two argument", func(t *testing.T) {
90+
in := []int{1, 2, 3}
91+
var out int
92+
93+
{
94+
err := godash.Reduce(in, &out, func() int { return 0 })
95+
assert.EqualError(t, err, "reducer function has to take exactly two argument")
96+
}
97+
98+
{
99+
err := godash.Reduce(in, &out, func(int) int { return 0 })
100+
assert.EqualError(t, err, "reducer function has to take exactly two argument")
101+
}
102+
})
103+
104+
t.Run("should not accept reducer function that do not return exactly one value", func(t *testing.T) {
105+
in := []int{1, 2, 3}
106+
var out int
107+
108+
{
109+
err := godash.Reduce(in, &out, func(int, int) {})
110+
assert.EqualError(t, err, "reducer function should return only one return value")
111+
}
112+
113+
{
114+
err := godash.Reduce(in, &out, func(int, int) (int, int) { return 0, 0 })
115+
assert.EqualError(t, err, "reducer function should return only one return value")
116+
}
117+
})
118+
119+
t.Run("should accept reducer function whose first argument's kind should be output's kind", func(t *testing.T) {
120+
in := []int{1, 2, 3}
121+
var out int
122+
123+
{
124+
err := godash.Reduce(in, &out, func(string, int) int { return 0 })
125+
assert.EqualError(t, err, "reducer function's first argument has to be the type of out")
126+
}
127+
128+
{
129+
err := godash.Reduce(in, &out, func(int, int) int { return 0 })
130+
assert.NoError(t, err)
131+
}
132+
})
133+
134+
t.Run("should accept reducer function whose second argument's kind should be input slice's element kind", func(t *testing.T) {
135+
in := []int{1, 2, 3}
136+
var out string
137+
138+
{
139+
err := godash.Reduce(in, &out, func(string, string) string { return "" })
140+
assert.EqualError(t, err, "reducer function's second argument has to be the type of element of input slice")
141+
}
142+
143+
{
144+
err := godash.Reduce(in, &out, func(string, int) string { return "" })
145+
assert.NoError(t, err)
146+
}
147+
})
148+
149+
t.Run("should accept reducer function whose return kind should be output's kind", func(t *testing.T) {
150+
in := []int{1, 2, 3}
151+
var out string
152+
153+
{
154+
err := godash.Reduce(in, &out, func(string, int) int { return 0 })
155+
assert.EqualError(t, err, "reducer function's return type has to be the type of out")
156+
}
157+
158+
{
159+
err := godash.Reduce(in, &out, func(string, int) string { return "" })
160+
assert.NoError(t, err)
161+
}
162+
})
163+
}

0 commit comments

Comments
 (0)