Skip to content

Commit

Permalink
Merge pull request #21 from prabhu43/master
Browse files Browse the repository at this point in the history
Support reflect.Map in Map function
  • Loading branch information
aswinkarthik committed Oct 25, 2019
2 parents 496735a + 218751c commit 7e5f9a5
Show file tree
Hide file tree
Showing 4 changed files with 228 additions and 4 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
language: go

go:
- 1.x
- stable

env:
global:
Expand Down
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,23 @@ func main() {
}
```

```go
func main() {
input := map[string]int{
"key1": 1,
"key2": 2,
"key3": 3,
}
var output []int

godash.Map(input, &output, func(el int) int {
return el * el
})

fmt.Println(output) // prints 1 4 9
}
```

### Filter

Filter out elements that fail the predicate.
Expand Down
40 changes: 37 additions & 3 deletions map.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,6 @@ func Map(in, out, mapperFn interface{}) error {
}

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

if mapperFnType.NumOut() != 1 {
return fmt.Errorf("mapper function should return only one return value")
Expand All @@ -40,6 +37,11 @@ func Map(in, out, mapperFn interface{}) error {
if output.Elem().Kind() != reflect.Slice {
return fmt.Errorf("output should be a slice for input of type slice")
}

if mapperFnType.NumIn() != 1 {
return fmt.Errorf("mapper function has to take only one argument")
}

if input.Type().Elem() != mapper.Type().In(0) {
return fmt.Errorf("mapper function's first argument (%s) has to be (%s)", mapper.Type().In(0), input.Type().Elem())
}
Expand All @@ -59,5 +61,37 @@ func Map(in, out, mapperFn interface{}) error {

return nil
}

if input.Kind() == reflect.Map {
if output.Elem().Kind() != reflect.Slice {
return fmt.Errorf("output should be a slice for input of type slice")
}

if mapperFnType.NumIn() != 2 {
return fmt.Errorf("mapper function has to take exactly two arguments")
}

if mapper.Type().In(0) != input.Type().Key() {
return fmt.Errorf("mapper function's first argument (%s) has to be (%s)", mapper.Type().In(0), input.Type().Key())
}
if mapper.Type().In(1) != input.Type().Elem() {
return fmt.Errorf("mapper function's second argument (%s) has to be (%s)", mapper.Type().In(1), input.Type().Elem())
}
if mapper.Type().Out(0) != output.Elem().Type().Elem() {
return fmt.Errorf("mapper function's return type has to be (%s) but is (%s)", mapper.Type().Out(0), output.Elem().Type().Elem())
}

result := reflect.MakeSlice(output.Elem().Type(), 0, input.Len())
for _, key := range input.MapKeys() {
value := input.MapIndex(key)

returnValues := mapper.Call([]reflect.Value{key, value})

result = reflect.Append(result, returnValues[0])
}
output.Elem().Set(result)

return nil
}
return fmt.Errorf("not implemented")
}
173 changes: 173 additions & 0 deletions map_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"github.com/stretchr/testify/assert"
"github.com/thecasualcoder/godash"
"sort"
"testing"
)

Expand Down Expand Up @@ -141,6 +142,164 @@ func TestMap(t *testing.T) {
})
}

func TestMapForMap(t *testing.T) {
t.Run("support primitive type values", func(t *testing.T) {
in := map[string]int{"key1": 1, "key2": 2, "key3": 3}
out := make([]int, 0)

err := godash.Map(in, &out, func(key string, value int) int {
return value * value
})

expected := []int{1, 4, 9}
assert.NoError(t, err)
assert.ElementsMatch(t, expected, out)
})

t.Run("support structs", func(t *testing.T) {
type person struct {
name string
}

in := map[string]person{
"person1": {name: "john"},
"person2": {name: "doe"},
}
out := make([]string, 0)
expected := []string{"john", "doe"}

err := godash.Map(in, &out, func(key string, value person) string {
return value.name
})

assert.NoError(t, err)
assert.ElementsMatch(t, expected, out)
})

squared := func(key string, value int) int {
return value * value
}

t.Run("should not panic if output is nil", func(t *testing.T) {
in := map[string]int{"key1": 1, "key2": 2, "key3": 3}

{
var out []int

err := godash.Map(in, out, squared)

assert.EqualError(t, err, "output is nil. Pass a reference to set output")
}

{
err := godash.Map(in, nil, squared)

assert.EqualError(t, err, "output is nil. Pass a reference to set output")
}
})

t.Run("should not panic if output is not a slice", func(t *testing.T) {
in := map[string]int{"key1": 1, "key2": 2, "key3": 3}

var out int

err := godash.Map(in, &out, squared)

assert.EqualError(t, err, "output should be a slice for input of type slice")
})

t.Run("should not accept mapper function that are not functions", func(t *testing.T) {
in := map[string]int{"key1": 1, "key2": 2, "key3": 3}
var out []int

err := godash.Map(in, &out, 7)

assert.EqualError(t, err, "mapperFn has to be a function")
})

t.Run("should not accept mapper function that do not take exactly two arguments", func(t *testing.T) {
in := map[string]int{"key1": 1, "key2": 2, "key3": 3}
var out []int

{
err := godash.Map(in, &out, func() int { return 0 })
assert.EqualError(t, err, "mapper function has to take exactly two arguments")
}

{
err := godash.Map(in, &out, func(int) int { return 0 })
assert.EqualError(t, err, "mapper function has to take exactly two arguments")
}

{
err := godash.Map(in, &out, func(int, int, int) int { return 0 })
assert.EqualError(t, err, "mapper function has to take exactly two arguments")
}
})

t.Run("should not accept mapper function that do not return exactly one value", func(t *testing.T) {
in := map[string]int{"key1": 1, "key2": 2, "key3": 3}
var out []int

{
err := godash.Map(in, &out, func(int, int) {})
assert.EqualError(t, err, "mapper function should return only one return value")
}

{
err := godash.Map(in, &out, func(int, int) (int, int) { return 0, 0 })
assert.EqualError(t, err, "mapper function should return only one return value")
}
})

t.Run("should accept mapper function whose first argument's kind should be map's key kind", func(t *testing.T) {
in := map[string]int{"key1": 1, "key2": 2, "key3": 3}

var out []int

{
err := godash.Map(in, &out, func(int, int) string { return "" })
assert.EqualError(t, err, "mapper function's first argument (int) has to be (string)")
}

{
err := godash.Map(in, &out, func(string, int) int { return 0 })
assert.NoError(t, err)
}
})

t.Run("should accept mapper function whose second argument's kind should be map's value kind", func(t *testing.T) {
in := map[string]int{"key1": 1, "key2": 2, "key3": 3}

var out []int

{
err := godash.Map(in, &out, func(string, string) string { return "" })
assert.EqualError(t, err, "mapper function's second argument (string) has to be (int)")
}

{
err := godash.Map(in, &out, func(string, int) int { return 0 })
assert.NoError(t, err)
}
})

t.Run("should accept mapper function whose return's kind should be output slice's element kind", func(t *testing.T) {
in := map[string]int{"key1": 1, "key2": 2, "key3": 3}
var out []string

{
err := godash.Map(in, &out, func(string, int) int { return 0 })
assert.EqualError(t, err, "mapper function's return type has to be (int) but is (string)")
}

{
err := godash.Map(in, &out, func(string, int) string { return "" })
assert.NoError(t, err)
}
})
}

func ExampleMap() {
input := []int{0, 1, 2, 3, 4}
var output []string
Expand All @@ -153,3 +312,17 @@ func ExampleMap() {

// Output: [0 1 4 9 16]
}

func ExampleMap_map() {
input := map[string]int{"key1": 1, "key2": 2, "key3": 3, "key4": 4, "key5": 5}
var output []int

_ = godash.Map(input, &output, func(key string, num int) int {
return num * num
})

sort.Ints(output)
fmt.Println(output)

// Unordered Output: [1 4 9 16 25]
}

0 comments on commit 7e5f9a5

Please sign in to comment.