Skip to content

Commit 7e5f9a5

Browse files
authored
Merge pull request #21 from prabhu43/master
Support reflect.Map in Map function
2 parents 496735a + 218751c commit 7e5f9a5

File tree

4 files changed

+228
-4
lines changed

4 files changed

+228
-4
lines changed

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
language: go
22

33
go:
4-
- 1.x
4+
- stable
55

66
env:
77
global:

README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,23 @@ func main() {
6363
}
6464
```
6565

66+
```go
67+
func main() {
68+
input := map[string]int{
69+
"key1": 1,
70+
"key2": 2,
71+
"key3": 3,
72+
}
73+
var output []int
74+
75+
godash.Map(input, &output, func(el int) int {
76+
return el * el
77+
})
78+
79+
fmt.Println(output) // prints 1 4 9
80+
}
81+
```
82+
6683
### Filter
6784

6885
Filter out elements that fail the predicate.

map.go

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,6 @@ func Map(in, out, mapperFn interface{}) error {
2828
}
2929

3030
mapperFnType := mapper.Type()
31-
if mapperFnType.NumIn() != 1 {
32-
return fmt.Errorf("mapper function has to take only one argument")
33-
}
3431

3532
if mapperFnType.NumOut() != 1 {
3633
return fmt.Errorf("mapper function should return only one return value")
@@ -40,6 +37,11 @@ func Map(in, out, mapperFn interface{}) error {
4037
if output.Elem().Kind() != reflect.Slice {
4138
return fmt.Errorf("output should be a slice for input of type slice")
4239
}
40+
41+
if mapperFnType.NumIn() != 1 {
42+
return fmt.Errorf("mapper function has to take only one argument")
43+
}
44+
4345
if input.Type().Elem() != mapper.Type().In(0) {
4446
return fmt.Errorf("mapper function's first argument (%s) has to be (%s)", mapper.Type().In(0), input.Type().Elem())
4547
}
@@ -59,5 +61,37 @@ func Map(in, out, mapperFn interface{}) error {
5961

6062
return nil
6163
}
64+
65+
if input.Kind() == reflect.Map {
66+
if output.Elem().Kind() != reflect.Slice {
67+
return fmt.Errorf("output should be a slice for input of type slice")
68+
}
69+
70+
if mapperFnType.NumIn() != 2 {
71+
return fmt.Errorf("mapper function has to take exactly two arguments")
72+
}
73+
74+
if mapper.Type().In(0) != input.Type().Key() {
75+
return fmt.Errorf("mapper function's first argument (%s) has to be (%s)", mapper.Type().In(0), input.Type().Key())
76+
}
77+
if mapper.Type().In(1) != input.Type().Elem() {
78+
return fmt.Errorf("mapper function's second argument (%s) has to be (%s)", mapper.Type().In(1), input.Type().Elem())
79+
}
80+
if mapper.Type().Out(0) != output.Elem().Type().Elem() {
81+
return fmt.Errorf("mapper function's return type has to be (%s) but is (%s)", mapper.Type().Out(0), output.Elem().Type().Elem())
82+
}
83+
84+
result := reflect.MakeSlice(output.Elem().Type(), 0, input.Len())
85+
for _, key := range input.MapKeys() {
86+
value := input.MapIndex(key)
87+
88+
returnValues := mapper.Call([]reflect.Value{key, value})
89+
90+
result = reflect.Append(result, returnValues[0])
91+
}
92+
output.Elem().Set(result)
93+
94+
return nil
95+
}
6296
return fmt.Errorf("not implemented")
6397
}

map_test.go

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"fmt"
55
"github.com/stretchr/testify/assert"
66
"github.com/thecasualcoder/godash"
7+
"sort"
78
"testing"
89
)
910

@@ -141,6 +142,164 @@ func TestMap(t *testing.T) {
141142
})
142143
}
143144

145+
func TestMapForMap(t *testing.T) {
146+
t.Run("support primitive type values", func(t *testing.T) {
147+
in := map[string]int{"key1": 1, "key2": 2, "key3": 3}
148+
out := make([]int, 0)
149+
150+
err := godash.Map(in, &out, func(key string, value int) int {
151+
return value * value
152+
})
153+
154+
expected := []int{1, 4, 9}
155+
assert.NoError(t, err)
156+
assert.ElementsMatch(t, expected, out)
157+
})
158+
159+
t.Run("support structs", func(t *testing.T) {
160+
type person struct {
161+
name string
162+
}
163+
164+
in := map[string]person{
165+
"person1": {name: "john"},
166+
"person2": {name: "doe"},
167+
}
168+
out := make([]string, 0)
169+
expected := []string{"john", "doe"}
170+
171+
err := godash.Map(in, &out, func(key string, value person) string {
172+
return value.name
173+
})
174+
175+
assert.NoError(t, err)
176+
assert.ElementsMatch(t, expected, out)
177+
})
178+
179+
squared := func(key string, value int) int {
180+
return value * value
181+
}
182+
183+
t.Run("should not panic if output is nil", func(t *testing.T) {
184+
in := map[string]int{"key1": 1, "key2": 2, "key3": 3}
185+
186+
{
187+
var out []int
188+
189+
err := godash.Map(in, out, squared)
190+
191+
assert.EqualError(t, err, "output is nil. Pass a reference to set output")
192+
}
193+
194+
{
195+
err := godash.Map(in, nil, squared)
196+
197+
assert.EqualError(t, err, "output is nil. Pass a reference to set output")
198+
}
199+
})
200+
201+
t.Run("should not panic if output is not a slice", func(t *testing.T) {
202+
in := map[string]int{"key1": 1, "key2": 2, "key3": 3}
203+
204+
var out int
205+
206+
err := godash.Map(in, &out, squared)
207+
208+
assert.EqualError(t, err, "output should be a slice for input of type slice")
209+
})
210+
211+
t.Run("should not accept mapper function that are not functions", func(t *testing.T) {
212+
in := map[string]int{"key1": 1, "key2": 2, "key3": 3}
213+
var out []int
214+
215+
err := godash.Map(in, &out, 7)
216+
217+
assert.EqualError(t, err, "mapperFn has to be a function")
218+
})
219+
220+
t.Run("should not accept mapper function that do not take exactly two arguments", func(t *testing.T) {
221+
in := map[string]int{"key1": 1, "key2": 2, "key3": 3}
222+
var out []int
223+
224+
{
225+
err := godash.Map(in, &out, func() int { return 0 })
226+
assert.EqualError(t, err, "mapper function has to take exactly two arguments")
227+
}
228+
229+
{
230+
err := godash.Map(in, &out, func(int) int { return 0 })
231+
assert.EqualError(t, err, "mapper function has to take exactly two arguments")
232+
}
233+
234+
{
235+
err := godash.Map(in, &out, func(int, int, int) int { return 0 })
236+
assert.EqualError(t, err, "mapper function has to take exactly two arguments")
237+
}
238+
})
239+
240+
t.Run("should not accept mapper function that do not return exactly one value", func(t *testing.T) {
241+
in := map[string]int{"key1": 1, "key2": 2, "key3": 3}
242+
var out []int
243+
244+
{
245+
err := godash.Map(in, &out, func(int, int) {})
246+
assert.EqualError(t, err, "mapper function should return only one return value")
247+
}
248+
249+
{
250+
err := godash.Map(in, &out, func(int, int) (int, int) { return 0, 0 })
251+
assert.EqualError(t, err, "mapper function should return only one return value")
252+
}
253+
})
254+
255+
t.Run("should accept mapper function whose first argument's kind should be map's key kind", func(t *testing.T) {
256+
in := map[string]int{"key1": 1, "key2": 2, "key3": 3}
257+
258+
var out []int
259+
260+
{
261+
err := godash.Map(in, &out, func(int, int) string { return "" })
262+
assert.EqualError(t, err, "mapper function's first argument (int) has to be (string)")
263+
}
264+
265+
{
266+
err := godash.Map(in, &out, func(string, int) int { return 0 })
267+
assert.NoError(t, err)
268+
}
269+
})
270+
271+
t.Run("should accept mapper function whose second argument's kind should be map's value kind", func(t *testing.T) {
272+
in := map[string]int{"key1": 1, "key2": 2, "key3": 3}
273+
274+
var out []int
275+
276+
{
277+
err := godash.Map(in, &out, func(string, string) string { return "" })
278+
assert.EqualError(t, err, "mapper function's second argument (string) has to be (int)")
279+
}
280+
281+
{
282+
err := godash.Map(in, &out, func(string, int) int { return 0 })
283+
assert.NoError(t, err)
284+
}
285+
})
286+
287+
t.Run("should accept mapper function whose return's kind should be output slice's element kind", func(t *testing.T) {
288+
in := map[string]int{"key1": 1, "key2": 2, "key3": 3}
289+
var out []string
290+
291+
{
292+
err := godash.Map(in, &out, func(string, int) int { return 0 })
293+
assert.EqualError(t, err, "mapper function's return type has to be (int) but is (string)")
294+
}
295+
296+
{
297+
err := godash.Map(in, &out, func(string, int) string { return "" })
298+
assert.NoError(t, err)
299+
}
300+
})
301+
}
302+
144303
func ExampleMap() {
145304
input := []int{0, 1, 2, 3, 4}
146305
var output []string
@@ -153,3 +312,17 @@ func ExampleMap() {
153312

154313
// Output: [0 1 4 9 16]
155314
}
315+
316+
func ExampleMap_map() {
317+
input := map[string]int{"key1": 1, "key2": 2, "key3": 3, "key4": 4, "key5": 5}
318+
var output []int
319+
320+
_ = godash.Map(input, &output, func(key string, num int) int {
321+
return num * num
322+
})
323+
324+
sort.Ints(output)
325+
fmt.Println(output)
326+
327+
// Unordered Output: [1 4 9 16 25]
328+
}

0 commit comments

Comments
 (0)