Skip to content

Commit e9684d4

Browse files
committed
Support maps in Map
Signed-off-by: Prabhu Jayakumar <[email protected]>
1 parent d71dcbb commit e9684d4

File tree

2 files changed

+208
-3
lines changed

2 files changed

+208
-3
lines changed

map.go

+37-3
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

+171
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,164 @@ func TestMap(t *testing.T) {
141141
})
142142
}
143143

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

154312
// Output: [0 1 4 9 16]
155313
}
314+
315+
func ExampleMapForMaps() {
316+
input := map[string]int{"key1": 1, "key2": 2, "key3": 3, "key4": 4, "key5": 5}
317+
var output []string
318+
319+
_ = godash.Map(input, &output, func(key string, num int) string {
320+
return fmt.Sprintf("%d", num*num)
321+
})
322+
323+
fmt.Println(output)
324+
325+
// Output: [0 1 4 9 16]
326+
}

0 commit comments

Comments
 (0)