├── .deepsource.toml ├── .github ├── ISSUE_TEMPLATE │ └── feature_request.md ├── contributing.md └── pull_request_template.md ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── all.go ├── all_test.go ├── any.go ├── any_test.go ├── common.go ├── filter.go ├── filter_test.go ├── find.go ├── find_test.go ├── go.mod ├── go.sum ├── map.go ├── map_test.go ├── reduce.go └── reduce_test.go /.deepsource.toml: -------------------------------------------------------------------------------- 1 | version = 1 2 | 3 | test_patterns = [ 4 | "*_test.go" 5 | ] 6 | 7 | exclude_patterns = [ 8 | "vendor/**" 9 | ] 10 | 11 | [[analyzers]] 12 | name = "go" 13 | enabled = true 14 | 15 | [analyzers.meta] 16 | import_path = "github.com/thecasualcoder/godash" 17 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: Add newAPI function 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | // Description of the new API 11 | 12 | API 13 | 14 | ```go 15 | input := inputCollection 16 | var output outputType 17 | 18 | godash.NewAPI(input, ...) 19 | 20 | fmt.Println(output) 21 | 22 | // Output: your output here 23 | ``` 24 | -------------------------------------------------------------------------------- /.github/contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing to Godash 2 | 3 | Thank you for contributing to Godash. 4 | 5 | When contributing to this repository, make sure to first raise an [Issue](https://github.com/thecasualcoder/godash/issues) about the requirements. 6 | The issue should have detailed API of how the function will work. Any special validations can be mentioned explicitly. 7 | 8 | ## Pull Request Process 9 | 10 | Ensure the PR checklist is completed: 11 | 12 | - Validations for types involved in the function. 13 | - Tests for the function that covers all branches. 14 | - All tests should pass. 15 | - ExampleTest of the function for GoDoc 16 | - README.md on how to use the functions 17 | - Comment on the function explaining how it works and the various validations done -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Checklist for PR 2 | 3 | [] Link to corresponding Issue. 4 | [] Tests covering all the code branches. 5 | [] Validations for all the argument's type. This includes the functions passed as arguments. 6 | [] A Comment on how the function works and what are the different validations in the function. 7 | [] An ExampleTest of the function. 8 | [] README.md should be updated about the function. -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Go ### 2 | # Binaries for programs and plugins 3 | *.exe 4 | *.exe~ 5 | *.dll 6 | *.so 7 | *.dylib 8 | 9 | # Test binary, built with `go test -c` 10 | *.test 11 | 12 | # Output of the go coverage tool, specifically when used with LiteIDE 13 | *.out 14 | 15 | # Dependency directories (remove the comment below to include it) 16 | # vendor/ 17 | 18 | ### Go Patch ### 19 | /vendor/ 20 | /Godeps/ 21 | 22 | 23 | 24 | ### Intellij ### 25 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 26 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 27 | .idea 28 | 29 | # User-specific stuff 30 | .idea/**/workspace.xml 31 | .idea/**/tasks.xml 32 | .idea/**/usage.statistics.xml 33 | .idea/**/dictionaries 34 | .idea/**/shelf 35 | 36 | # Generated files 37 | .idea/**/contentModel.xml 38 | 39 | # Sensitive or high-churn files 40 | .idea/**/dataSources/ 41 | .idea/**/dataSources.ids 42 | .idea/**/dataSources.local.xml 43 | .idea/**/sqlDataSources.xml 44 | .idea/**/dynamic.xml 45 | .idea/**/uiDesigner.xml 46 | .idea/**/dbnavigator.xml 47 | 48 | # Gradle 49 | .idea/**/gradle.xml 50 | .idea/**/libraries 51 | 52 | # Gradle and Maven with auto-import 53 | # When using Gradle or Maven with auto-import, you should exclude module files, 54 | # since they will be recreated, and may cause churn. Uncomment if using 55 | # auto-import. 56 | # .idea/modules.xml 57 | # .idea/*.iml 58 | # .idea/modules 59 | # *.iml 60 | # *.ipr 61 | 62 | # CMake 63 | cmake-build-*/ 64 | 65 | # Mongo Explorer plugin 66 | .idea/**/mongoSettings.xml 67 | 68 | # File-based project format 69 | *.iws 70 | 71 | # IntelliJ 72 | out/ 73 | 74 | # mpeltonen/sbt-idea plugin 75 | .idea_modules/ 76 | 77 | # JIRA plugin 78 | atlassian-ide-plugin.xml 79 | 80 | # Cursive Clojure plugin 81 | .idea/replstate.xml 82 | 83 | # Crashlytics plugin (for Android Studio and IntelliJ) 84 | com_crashlytics_export_strings.xml 85 | crashlytics.properties 86 | crashlytics-build.properties 87 | fabric.properties 88 | 89 | # Editor-based Rest Client 90 | .idea/httpRequests 91 | 92 | # Android studio 3.1+ serialized cache file 93 | .idea/caches/build_file_checksums.ser 94 | 95 | ### Intellij Patch ### 96 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 97 | 98 | # *.iml 99 | # modules.xml 100 | # .idea/misc.xml 101 | # *.ipr 102 | 103 | # Sonarlint plugin 104 | .idea/**/sonarlint/ 105 | 106 | # SonarQube Plugin 107 | .idea/**/sonarIssues.xml 108 | 109 | # Markdown Navigator plugin 110 | .idea/**/markdown-navigator.xml 111 | .idea/**/markdown-navigator/ 112 | 113 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - stable 5 | 6 | env: 7 | global: 8 | - GO111MODULE=on 9 | 10 | script: 11 | - go test -v -race -cover ./... 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 The Casual Coder 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Godash 2 | 3 | [![Build Status](https://travis-ci.org/thecasualcoder/godash.svg?branch=master)](https://travis-ci.org/thecasualcoder/godash) 4 | [![Go Doc](https://godoc.org/github.com/thecasualcoder/godash?status.svg)](https://godoc.org/github.com/thecasualcoder/godash) 5 | 6 | [![DeepSource](https://static.deepsource.io/deepsource-badge-light.svg)](https://deepsource.io/gh/thecasualcoder/godash/?ref=repository-badge) 7 | 8 | Inspired from [Lodash](https://github.com/lodash/lodash) for golang 9 | 10 | ## Why? 11 | 12 | - I did not like most map/reduce implementations that returned an `interface{}` which had to be typecasted. This library follows the concept of how `json.Marshal` works. Create an output variable **outside** the functions and pass a **pointer reference** to it, so it can be **set**. 13 | - This library heavily makes use of `reflect` package and hence will have an **impact on performance**. **DO NOT USE THIS IN PRODUCTION**. This repository is more of a way to learn the reflect package and measure its performance impact. 14 | - All functions have **validations** on how mapper function/predicate functions should be written. So even if we lose out on compile time validation, the library still **does not panic** if it does not know how to handle an argument passed to it. 15 | 16 | ## Available Functions 17 | 18 | 1. [Map](#Map) 19 | 2. [Filter](#Filter) 20 | 3. [Reduce](#Reduce) 21 | 4. [Any](#Any-or-Some) or [Some](#Any-or-Some) 22 | 5. [Find](#Find) 23 | 6. [All](#All-or-Every) or [Every](#All-or-Every) 24 | 25 | ## Usages 26 | 27 | ### Map 28 | 29 | Map applies a mapper function on each element of an input and sets it in output. 30 | For more [docs](https://godoc.org/github.com/thecasualcoder/godash#Map). 31 | 32 | ```go 33 | func main() { 34 | input := []int{1, 2, 3, 4, 5} 35 | var output []int 36 | 37 | godash.Map(input, &output, func(el int) int { 38 | return el * el 39 | }) 40 | 41 | fmt.Println(output) // prints 1 4 9 16 25 42 | } 43 | ``` 44 | 45 | ```go 46 | type Person struct { 47 | Name string 48 | Age Int 49 | } 50 | 51 | func main() { 52 | input := []Person{ 53 | {Name: "John", Age: 22}, 54 | {Name: "Doe", Age: 23}, 55 | } 56 | var output []string 57 | 58 | godash.Map(input, &output, func(person Person) string { 59 | return person.Name 60 | }) 61 | 62 | fmt.Println(output) // prints John Doe 63 | } 64 | ``` 65 | 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 | 83 | ### Filter 84 | 85 | Filter out elements that fail the predicate. 86 | For more [docs](https://godoc.org/github.com/thecasualcoder/godash#Filter). 87 | 88 | ```go 89 | func main() { 90 | input := []int{1, 2, 3, 4, 5} 91 | var output []int 92 | 93 | godash.Filter(input, &output, func(element int) bool { 94 | return element % 2 == 0 95 | }) 96 | 97 | fmt.Println(output) // prints 2 4 98 | } 99 | ``` 100 | 101 | ```go 102 | func main() { 103 | input := []Person{ 104 | {Name: "John", Age: 20}, 105 | {Name: "Doe", Age: 30}, 106 | } 107 | var output []string 108 | 109 | godash.Filter(input, &output, func(person Person) bool { 110 | return person.Age > 25 111 | }) 112 | 113 | fmt.Println(output) // prints {Doe 30} 114 | } 115 | ``` 116 | 117 | ### Reduce 118 | 119 | Reduce can accept a reducer and apply the reducer on each element of the input slice while providing an accumulator to save the reduce output. 120 | For more [docs](https://godoc.org/github.com/thecasualcoder/godash#Reduce). 121 | 122 | ```go 123 | func main() { 124 | input := []string{"count", "words", "and", "print", "words", "count"} 125 | accumulator := map[string]int{} 126 | 127 | _ = godash.Reduce(input, &accumulator, func(acc map[string]int, element string) map[string]int { 128 | if _, present := acc[element]; present { 129 | acc[element] = acc[element] + 1 130 | } else { 131 | acc[element] = 1 132 | } 133 | return acc 134 | }) 135 | 136 | bytes, _ := json.MarshalIndent(accumulator, "", " ") 137 | fmt.Println(string(bytes)) 138 | 139 | // Output: 140 | //{ 141 | // "and": 1, 142 | // "count": 2, 143 | // "print": 1, 144 | // "words": 2 145 | //} 146 | 147 | } 148 | ``` 149 | 150 | ```go 151 | func main() { 152 | input := []Person{ 153 | {Name: "John", Age: 22}, 154 | {Name: "Doe", Age: 23}, 155 | } 156 | var output int 157 | 158 | godash.Reduce(input, &output, func(sum int, person Person) int { 159 | return sum + person.Age 160 | }) 161 | 162 | fmt.Println(output) // prints 45 163 | } 164 | ``` 165 | 166 | ### Any or Some 167 | 168 | Any or Some checks if predicate returns truthy for any element of collection. Iteration is stopped once predicate returns truthy. 169 | For more [docs](https://godoc.org/github.com/thecasualcoder/godash#Any). 170 | 171 | 172 | ```go 173 | func main() { 174 | input := []int{1, 2, 3, 4, 5} 175 | var output []int 176 | output, _ := godash.Any(input, func(num int) bool { 177 | return num % 7 == 0 178 | }) 179 | fmt.Println(output) // prints false 180 | } 181 | ``` 182 | 183 | ```go 184 | func main() { 185 | input := []Person{ 186 | {Name: "John", Age: 25}, 187 | {Name: "Doe", Age: 15}, 188 | } 189 | var output int 190 | output, _ := godash.Some(input, func(person Person) bool { 191 | return person.Age < 18 192 | }) 193 | fmt.Println(output) // prints true 194 | } 195 | ``` 196 | 197 | 198 | ### Find 199 | 200 | Returns the first element which passes the predicate. 201 | For more [docs](https://godoc.org/github.com/thecasualcoder/godash#Find). 202 | ```go 203 | func main() { 204 | input := []string{"john","wick","will"} 205 | var output string 206 | 207 | godash.Find(input, &output, func(element string) bool { 208 | return strings.HasPrefix(element, "w") // starts with 209 | } 210 | // output is "wick" 211 | fmt.Println(output) 212 | } 213 | ``` 214 | 215 | ### All or Every 216 | 217 | All or Every checks if predicate returns truthy for all element of collection. Iteration is stopped once predicate returns falsely. 218 | For more [docs](https://godoc.org/github.com/thecasualcoder/godash#All). 219 | 220 | ```go 221 | func main() { 222 | input := []int{1, 2, 3, 4, 5} 223 | var output bool 224 | output, _ := godash.All(input, func(num int) bool { 225 | return num >= 1 226 | }) 227 | fmt.Println(output) // prints true 228 | } 229 | ``` 230 | 231 | ```go 232 | func main() { 233 | input := []Person{ 234 | {Name: "John", Age: 25}, 235 | {Name: "Doe", Age: 15}, 236 | } 237 | var output bool 238 | output, _ := godash.Every(input, func(person Person) bool { 239 | return person.Age < 18 240 | }) 241 | fmt.Println(output) // prints false 242 | } 243 | -------------------------------------------------------------------------------- /all.go: -------------------------------------------------------------------------------- 1 | package godash 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | // All checks if predicate returns truthy for all element of collection. Iteration is stopped once predicate returns falsely. 9 | // Currently, input of type slice is supported 10 | // 11 | // Validations: 12 | // 13 | // 1. Predicate function should take one argument and return one value 14 | // 2. Predicate function should return a bool value 15 | // 3. Predicate function's argument should be of the same type as the elements of the input slice 16 | // 17 | // Validation errors are returned to the caller 18 | func All(in, predicateFn interface{}) (bool, error) { 19 | 20 | input := reflect.ValueOf(in) 21 | predicate := reflect.ValueOf(predicateFn) 22 | 23 | if predicate.Kind() != reflect.Func { 24 | return false, fmt.Errorf("predicateFn has to be a function") 25 | } 26 | 27 | predicateFnType := predicate.Type() 28 | if predicateFnType.NumIn() != 1 { 29 | return false, fmt.Errorf("predicate function has to take only one argument") 30 | } 31 | 32 | if predicateFnType.NumOut() != 1 { 33 | return false, fmt.Errorf("predicate function should return only one return value") 34 | } 35 | 36 | if predicateFnType.Out(0).Kind() != reflect.Bool { 37 | return false, fmt.Errorf("predicate function should return a boolean value") 38 | } 39 | 40 | inputKind := input.Kind() 41 | if inputKind == reflect.Slice { 42 | inputSliceElemType := input.Type().Elem 43 | predicateFnArgType := predicateFnType.In(0) 44 | if inputSliceElemType() != predicateFnArgType { 45 | return false, fmt.Errorf("predicate function's argument (%s) has to be (%s)", predicateFnArgType, inputSliceElemType()) 46 | } 47 | 48 | for i := 0; i < input.Len(); i++ { 49 | arg := input.Index(i) 50 | returnValue := predicate.Call([]reflect.Value{arg})[0] 51 | if !returnValue.Bool() { 52 | return false, nil 53 | } 54 | } 55 | 56 | return true, nil 57 | } 58 | 59 | return false, fmt.Errorf("not implemented for (%s)", inputKind) 60 | } 61 | 62 | // Every is an alias for All function 63 | func Every(in, predicateFn interface{}) (bool, error) { 64 | return All(in, predicateFn) 65 | } 66 | -------------------------------------------------------------------------------- /all_test.go: -------------------------------------------------------------------------------- 1 | package godash_test 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | "github.com/thecasualcoder/godash" 9 | ) 10 | 11 | func TestAllAndEvery(t *testing.T) { 12 | var funcs = map[string]func(interface{}, interface{}) (bool, error){ 13 | "All()": godash.All, 14 | "Every()": godash.Every, 15 | } 16 | 17 | for fnName, fn := range funcs { 18 | t.Run(fmt.Sprintf("%s should return err if predicate is not a function", fnName), func(t *testing.T) { 19 | in := []int{1, 2, 3} 20 | 21 | _, err := fn(in, "not a func") 22 | 23 | assert.EqualError(t, err, "predicateFn has to be a function") 24 | }) 25 | 26 | t.Run(fmt.Sprintf("%s should return err if predicate function do not take exactly one argument", fnName), func(t *testing.T) { 27 | in := []int{1, 2, 3} 28 | 29 | { 30 | _, err := fn(in, func() {}) 31 | 32 | assert.EqualError(t, err, "predicate function has to take only one argument") 33 | } 34 | { 35 | _, err := fn(in, func(int, int) {}) 36 | 37 | assert.EqualError(t, err, "predicate function has to take only one argument") 38 | } 39 | }) 40 | 41 | t.Run(fmt.Sprintf("%s should return err if predicate function do not return exactly one value", fnName), func(t *testing.T) { 42 | in := []int{1, 2, 3} 43 | 44 | { 45 | _, err := fn(in, func(int) {}) 46 | 47 | assert.EqualError(t, err, "predicate function should return only one return value") 48 | } 49 | { 50 | _, err := fn(in, func(int) (bool, bool) { return true, true }) 51 | 52 | assert.EqualError(t, err, "predicate function should return only one return value") 53 | 54 | } 55 | }) 56 | 57 | t.Run(fmt.Sprintf("%s should return err if predicate function's return value is not a boolean", fnName), func(t *testing.T) { 58 | in := []int{1, 2, 3} 59 | 60 | _, err := fn(in, func(int) int { return 0 }) 61 | 62 | assert.EqualError(t, err, "predicate function should return a boolean value") 63 | }) 64 | 65 | t.Run(fmt.Sprintf("%s should return err if input is not a slice", fnName), func(t *testing.T) { 66 | in := 1 67 | 68 | _, err := fn(in, func(int) bool { return true }) 69 | 70 | assert.EqualError(t, err, "not implemented for (int)") 71 | }) 72 | 73 | t.Run(fmt.Sprintf("%s should return err if there is a type mismatch between predicate function's argument and input slice", fnName), func(t *testing.T) { 74 | in := []string{"hello", "world"} 75 | 76 | _, err := fn(in, func(int) bool { return true }) 77 | 78 | assert.EqualError(t, err, "predicate function's argument (int) has to be (string)") 79 | }) 80 | 81 | t.Run(fmt.Sprintf("%s should return true if predicate passes for all element in input slice", fnName), func(t *testing.T) { 82 | in := []int{1, 3, 5, 7, 9, 11, 13} 83 | 84 | output, err := fn(in, func(elem int) bool { return elem%2 == 1 }) 85 | 86 | assert.NoError(t, err) 87 | assert.True(t, output) 88 | }) 89 | 90 | t.Run(fmt.Sprintf("%s should return false if predicate fails for at least one of the elements in input slice", fnName), func(t *testing.T) { 91 | in := []int{1, 2, 5, 7, 9, 11, 13} 92 | 93 | output, err := fn(in, func(num int) bool { return num%2 == 1 }) 94 | 95 | assert.NoError(t, err) 96 | assert.False(t, output) 97 | }) 98 | 99 | t.Run(fmt.Sprintf("%s should support structs", fnName), func(t *testing.T) { 100 | type person struct { 101 | name string 102 | age int 103 | } 104 | in := []person{ 105 | {name: "John", age: 12}, 106 | {name: "Doe", age: 25}, 107 | } 108 | 109 | { 110 | output, err := fn(in, func(person person) bool { return person.age < 18 }) 111 | 112 | assert.NoError(t, err) 113 | assert.False(t, output) 114 | } 115 | { 116 | output, err := fn(in, func(person person) bool { return person.age < 30 }) 117 | 118 | assert.NoError(t, err) 119 | assert.True(t, output) 120 | } 121 | }) 122 | } 123 | } 124 | 125 | func ExampleAll() { 126 | input := []int{0, 1, 2, 3, 4} 127 | 128 | output, _ := godash.All(input, func(num int) bool { 129 | return num >= 0 130 | }) 131 | 132 | fmt.Println(output) 133 | 134 | // Output: true 135 | } 136 | 137 | func ExampleEvery() { 138 | input := []int{0, 1, 2, 3, 4} 139 | 140 | output, _ := godash.Every(input, func(num int) bool { 141 | return num >= 0 142 | }) 143 | 144 | fmt.Println(output) 145 | 146 | // Output: true 147 | } 148 | -------------------------------------------------------------------------------- /any.go: -------------------------------------------------------------------------------- 1 | package godash 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | // Any checks if predicate returns truthy for any element of collection. Iteration is stopped once predicate returns truthy. 9 | // Currently, input of type slice is supported 10 | // 11 | // Validations: 12 | // 13 | // 1. Predicate function should take one argument and return one value 14 | // 2. Predicate function should return a bool value 15 | // 3. Predicate function's argument should be of the same type as the elements of the input slice 16 | // 17 | // Validation errors are returned to the caller 18 | func Any(in, predicateFn interface{}) (bool, error) { 19 | var output bool 20 | input := reflect.ValueOf(in) 21 | predicate := reflect.ValueOf(predicateFn) 22 | 23 | if predicate.Kind() != reflect.Func { 24 | return output, fmt.Errorf("predicateFn has to be a function") 25 | } 26 | 27 | predicateFnType := predicate.Type() 28 | if predicateFnType.NumIn() != 1 { 29 | return output, fmt.Errorf("predicate function has to take only one argument") 30 | } 31 | 32 | if predicateFnType.NumOut() != 1 { 33 | return output, fmt.Errorf("predicate function should return only one return value") 34 | } 35 | 36 | if predicateFnType.Out(0).Kind() != reflect.Bool { 37 | return output, fmt.Errorf("predicate function should return a boolean value") 38 | } 39 | 40 | inputKind := input.Kind() 41 | if inputKind == reflect.Slice { 42 | inputSliceElemType := input.Type().Elem 43 | predicateFnArgType := predicateFnType.In(0) 44 | if inputSliceElemType() != predicateFnArgType { 45 | return output, fmt.Errorf("predicate function's argument (%s) has to be (%s)", predicateFnArgType, inputSliceElemType()) 46 | } 47 | 48 | for i := 0; i < input.Len(); i++ { 49 | arg := input.Index(i) 50 | returnValue := predicate.Call([]reflect.Value{arg})[0] 51 | if returnValue.Bool() { 52 | return true, nil 53 | } 54 | } 55 | 56 | return output, nil 57 | } 58 | 59 | return output, fmt.Errorf("not implemented for (%s)", inputKind) 60 | } 61 | 62 | // Some is an alias for Any function 63 | func Some(in, predicateFn interface{}) (bool, error) { 64 | return Any(in, predicateFn) 65 | } 66 | -------------------------------------------------------------------------------- /any_test.go: -------------------------------------------------------------------------------- 1 | package godash_test 2 | 3 | import ( 4 | "fmt" 5 | "github.com/stretchr/testify/assert" 6 | "github.com/thecasualcoder/godash" 7 | "testing" 8 | ) 9 | 10 | func TestSomeAndAny(t *testing.T) { 11 | var funcs = map[string]func(interface{}, interface{}) (bool, error){ 12 | "Any()": godash.Any, 13 | "Some()": godash.Some, 14 | } 15 | 16 | for fnName, fn := range funcs { 17 | t.Run(fmt.Sprintf("%s should return err if predicate is not a function", fnName), func(t *testing.T) { 18 | in := []int{1, 2, 3} 19 | 20 | _, err := fn(in, "not a func") 21 | 22 | assert.EqualError(t, err, "predicateFn has to be a function") 23 | }) 24 | 25 | t.Run(fmt.Sprintf("%s should return err if predicate function do not take exactly one argument", fnName), func(t *testing.T) { 26 | in := []int{1, 2, 3} 27 | 28 | { 29 | _, err := fn(in, func() {}) 30 | 31 | assert.EqualError(t, err, "predicate function has to take only one argument") 32 | } 33 | { 34 | _, err := fn(in, func(int, int) {}) 35 | 36 | assert.EqualError(t, err, "predicate function has to take only one argument") 37 | } 38 | }) 39 | 40 | t.Run(fmt.Sprintf("%s should return err if predicate function do not return exactly one value", fnName), func(t *testing.T) { 41 | in := []int{1, 2, 3} 42 | 43 | { 44 | _, err := fn(in, func(int) {}) 45 | 46 | assert.EqualError(t, err, "predicate function should return only one return value") 47 | } 48 | { 49 | _, err := fn(in, func(int) (bool, bool) { return true, true }) 50 | 51 | assert.EqualError(t, err, "predicate function should return only one return value") 52 | 53 | } 54 | }) 55 | 56 | t.Run(fmt.Sprintf("%s should return err if predicate function's return value is not a boolean", fnName), func(t *testing.T) { 57 | in := []int{1, 2, 3} 58 | 59 | _, err := fn(in, func(int) int { return 0 }) 60 | 61 | assert.EqualError(t, err, "predicate function should return a boolean value") 62 | }) 63 | 64 | t.Run(fmt.Sprintf("%s should return err if input is not a slice", fnName), func(t *testing.T) { 65 | in := 1 66 | 67 | _, err := fn(in, func(int) bool { return true }) 68 | 69 | assert.EqualError(t, err, "not implemented for (int)") 70 | }) 71 | 72 | t.Run(fmt.Sprintf("%s should return err if there is a type mismatch between predicate function's argument and input slice", fnName), func(t *testing.T) { 73 | in := []string{"hello", "world"} 74 | 75 | _, err := fn(in, func(int) bool { return true }) 76 | 77 | assert.EqualError(t, err, "predicate function's argument (int) has to be (string)") 78 | }) 79 | 80 | t.Run(fmt.Sprintf("%s should return true if predicate passes for at least one of the element in input slice", fnName), func(t *testing.T) { 81 | in := []int{1, 1, 2, 3, 5, 8, 13} 82 | 83 | output, err := fn(in, func(elem int) bool { return elem%5 == 0 }) 84 | 85 | assert.NoError(t, err) 86 | assert.True(t, output) 87 | }) 88 | 89 | t.Run(fmt.Sprintf("%s should return false if predicate fails for all the elements in input slice", fnName), func(t *testing.T) { 90 | in := []int{1, 1, 2, 3, 5, 8, 13} 91 | 92 | output, err := fn(in, func(num int) bool { return num%6 == 0 }) 93 | 94 | assert.NoError(t, err) 95 | assert.False(t, output) 96 | }) 97 | 98 | t.Run(fmt.Sprintf("%s should support structs", fnName), func(t *testing.T) { 99 | type person struct { 100 | name string 101 | age int 102 | } 103 | in := []person{ 104 | {name: "John", age: 12}, 105 | {name: "Doe", age: 25}, 106 | } 107 | 108 | { 109 | output, err := fn(in, func(person person) bool { return person.age < 18 }) 110 | 111 | assert.NoError(t, err) 112 | assert.True(t, output) 113 | } 114 | { 115 | output, err := fn(in, func(person person) bool { return person.age < 30 }) 116 | 117 | assert.NoError(t, err) 118 | assert.True(t, output) 119 | } 120 | }) 121 | } 122 | } 123 | 124 | func ExampleAny() { 125 | input := []int{0, 1, 2, 3, 4} 126 | 127 | output, _ := godash.Any(input, func(num int) bool { 128 | return num%3 == 0 129 | }) 130 | 131 | fmt.Println(output) 132 | 133 | // Output: true 134 | } 135 | 136 | func ExampleSome() { 137 | input := []int{0, 1, 2, 3, 4} 138 | 139 | output, _ := godash.Some(input, func(num int) bool { 140 | return num%3 == 0 141 | }) 142 | 143 | fmt.Println(output) 144 | 145 | // Output: true 146 | } 147 | -------------------------------------------------------------------------------- /common.go: -------------------------------------------------------------------------------- 1 | package godash 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | func validateOut(output reflect.Value) error { 9 | zeroValue := reflect.Value{} 10 | if output == zeroValue { 11 | return fmt.Errorf("output is nil. Pass a reference to set output") 12 | } 13 | 14 | if output.IsNil() { 15 | return fmt.Errorf("output is nil. Pass a reference to set output") 16 | } 17 | 18 | if !output.Elem().CanSet() { 19 | return fmt.Errorf("cannot set out. Pass a reference to set output") 20 | } 21 | 22 | return nil 23 | } 24 | -------------------------------------------------------------------------------- /filter.go: -------------------------------------------------------------------------------- 1 | package godash 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | // Filter out elements that fail the predicate. 9 | // 10 | // Input of type slice is supported as of now. 11 | // Output is a slice in which filtered-in elements are stored. 12 | // PredicateFn function is applied on each element of input to determine to filter or not 13 | // 14 | // Validations: 15 | // 16 | // 1. Input and Output's slice should be of same type 17 | // 2. Predicate function can take one argument and return one argument 18 | // 3. Predicate's return argument is always boolean. 19 | // 4. Predicate's input argument should be input/output slice's element type. 20 | // 21 | // Validation errors are returned to the caller. 22 | func Filter(in, out, predicateFn interface{}) error { 23 | input := reflect.ValueOf(in) 24 | 25 | output := reflect.ValueOf(out) 26 | if err := validateOut(output); err != nil { 27 | return err 28 | } 29 | if input.Type() != output.Elem().Type() { 30 | return fmt.Errorf("input(%s) and output(%s) should be of the same Type", input.Type(), output.Elem().Type()) 31 | } 32 | 33 | predicate := reflect.ValueOf(predicateFn) 34 | if predicate.Type().NumOut() != 1 { 35 | return fmt.Errorf("predicate function should return only one return value - a boolean") 36 | } 37 | if predicateType := predicate.Type().Out(0).Kind(); predicateType != reflect.Bool { 38 | return fmt.Errorf("predicate function should return only a (boolean) and not a (%s)", predicateType) 39 | } 40 | 41 | if input.Kind() == reflect.Slice { 42 | { 43 | if input.Type().Elem().Kind() != predicate.Type().In(0).Kind() { 44 | return fmt.Errorf( 45 | "predicate function's first argument has to be the type (%s) instead of (%s)", 46 | input.Type().Elem(), 47 | predicate.Type().In(0), 48 | ) 49 | } 50 | } 51 | 52 | result := reflect.MakeSlice(output.Elem().Type(), 0, input.Len()) 53 | for i := 0; i < input.Len(); i++ { 54 | arg := input.Index(i) 55 | 56 | returnValues := predicate.Call([]reflect.Value{arg}) 57 | predicatePassed := returnValues[0].Bool() 58 | 59 | if predicatePassed { 60 | result = reflect.Append(result, arg) 61 | } 62 | } 63 | output.Elem().Set(result) 64 | 65 | return nil 66 | } 67 | return fmt.Errorf("not implemented") 68 | } 69 | -------------------------------------------------------------------------------- /filter_test.go: -------------------------------------------------------------------------------- 1 | package godash_test 2 | 3 | import ( 4 | "fmt" 5 | "github.com/stretchr/testify/assert" 6 | "github.com/thecasualcoder/godash" 7 | "testing" 8 | ) 9 | 10 | func TestFilter(t *testing.T) { 11 | t.Run("should filter elements that fail predicate", func(t *testing.T) { 12 | input := []int{1, 2, 3, 4, 5, 6, 7, 8} 13 | var output []int 14 | 15 | err := godash.Filter(input, &output, func(a int) bool { 16 | return a%2 == 0 17 | }) 18 | expected := []int{2, 4, 6, 8} 19 | 20 | assert.NoError(t, err) 21 | assert.Equal(t, expected, output) 22 | }) 23 | 24 | t.Run("should struct types", func(t *testing.T) { 25 | type person struct { 26 | age int 27 | } 28 | input := []person{ 29 | {30}, 30 | {20}, 31 | {40}, 32 | {10}, 33 | } 34 | var output []person 35 | 36 | err := godash.Filter(input, &output, func(p person) bool { 37 | return p.age > 20 38 | }) 39 | expected := []person{ 40 | {30}, 41 | {40}, 42 | } 43 | 44 | assert.NoError(t, err) 45 | assert.Equal(t, expected, output) 46 | }) 47 | 48 | t.Run("should validate predicate's arg", func(t *testing.T) { 49 | input := []int{1, 2, 3, 4, 5, 6, 7, 8} 50 | var output []int 51 | 52 | err := godash.Filter(input, &output, func(a string) bool { 53 | return a == "" 54 | }) 55 | 56 | assert.EqualError(t, err, "predicate function's first argument has to be the type (int) instead of (string)") 57 | }) 58 | 59 | t.Run("should validate predicate's return type", func(t *testing.T) { 60 | input := []int{1, 2, 3, 4, 5, 6, 7, 8} 61 | var output []int 62 | 63 | { 64 | err := godash.Filter(input, &output, func(a int) int { 65 | return a 66 | }) 67 | assert.EqualError(t, err, "predicate function should return only a (boolean) and not a (int)") 68 | } 69 | { 70 | err := godash.Filter(input, &output, func(int) (int, bool) { 71 | return 1, true 72 | }) 73 | assert.EqualError(t, err, "predicate function should return only one return value - a boolean") 74 | } 75 | }) 76 | 77 | t.Run("should validate output's type", func(t *testing.T) { 78 | input := []int{1, 2, 3, 4, 5, 6, 7, 8} 79 | var output []string 80 | 81 | err := godash.Filter(input, &output, func(a int) bool { 82 | return a == 0 83 | }) 84 | 85 | assert.EqualError(t, err, "input([]int) and output([]string) should be of the same Type") 86 | }) 87 | 88 | t.Run("should not panic if output is nil", func(t *testing.T) { 89 | in := []int{1, 2, 3} 90 | { 91 | var out []int 92 | 93 | err := godash.Filter(in, out, func(int) bool { return true }) 94 | 95 | assert.EqualError(t, err, "output is nil. Pass a reference to set output") 96 | } 97 | { 98 | err := godash.Filter(in, nil, func(int) bool { return true }) 99 | 100 | assert.EqualError(t, err, "output is nil. Pass a reference to set output") 101 | } 102 | }) 103 | } 104 | 105 | func ExampleFilter() { 106 | input := []string{ 107 | "rhythm", 108 | "of", 109 | "life", 110 | } 111 | var output []string 112 | 113 | _ = godash.Filter(input, &output, func(in string) bool { 114 | return len(in) > 3 115 | }) 116 | 117 | fmt.Println(output) 118 | 119 | // Output: 120 | // [rhythm life] 121 | } 122 | -------------------------------------------------------------------------------- /find.go: -------------------------------------------------------------------------------- 1 | package godash 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | // Find out elements. 9 | // 10 | // Input of type slice is supported as of now. 11 | // Output is a elements are matched. 12 | // PredicateFn function is applied on each element of input to determine to find element until it finds the element 13 | // 14 | // Validations: 15 | // 16 | // 1. Input's element type and Output should be of same type 17 | // 2. Predicate function can take one argument and return one argument 18 | // 3. Predicate's return argument is always boolean. 19 | // 4. Predicate's input argument should be output element type. 20 | // 21 | // Validation errors are returned to the caller 22 | func Find(in, out, predicateFn interface{}) error { 23 | input := reflect.ValueOf(in) 24 | output := reflect.ValueOf(out) 25 | inputTypeElem := input.Type().Elem() 26 | if inputTypeElem != output.Elem().Type() { 27 | return fmt.Errorf("input slice (%s) and output (%s) should be of the same Type", inputTypeElem, output.Elem().Type()) 28 | } 29 | 30 | predicate := reflect.ValueOf(predicateFn) 31 | if predicate.Type().NumOut() != 1 { 32 | return fmt.Errorf("predicate function should return only one return value - a boolean") 33 | } 34 | if predicateType := predicate.Type().Out(0).Kind(); predicateType != reflect.Bool { 35 | return fmt.Errorf("predicate function should return only a (boolean) and not a (%s)", predicateType) 36 | } 37 | if input.Kind() == reflect.Slice { 38 | { 39 | if inputTypeElem.Kind() != predicate.Type().In(0).Kind() { 40 | return fmt.Errorf( 41 | "predicate function's first argument has to be the type (%s) instead of (%s)", 42 | inputTypeElem, 43 | predicate.Type().In(0), 44 | ) 45 | } 46 | } 47 | for i := 0; i < input.Len(); i++ { 48 | arg := input.Index(i) 49 | 50 | returnValues := predicate.Call([]reflect.Value{arg}) 51 | predicatePassed := returnValues[0].Bool() 52 | 53 | if predicatePassed { 54 | output.Elem().Set(arg) 55 | return nil 56 | } 57 | } 58 | return fmt.Errorf("element not found") 59 | } 60 | return fmt.Errorf("not implemented") 61 | } 62 | -------------------------------------------------------------------------------- /find_test.go: -------------------------------------------------------------------------------- 1 | package godash_test 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | "github.com/thecasualcoder/godash" 10 | ) 11 | 12 | func TestFind(t *testing.T) { 13 | t.Run("should filter elements that fail predicate", func(t *testing.T) { 14 | input := []int{1, 2, 3, 4, 5, 6, 7, 8} 15 | var output int 16 | 17 | err := godash.Find(input, &output, func(a int) bool { 18 | return a == 1 // starts with 19 | }) 20 | expected := 1 21 | assert.NoError(t, err) 22 | assert.Equal(t, expected, output) 23 | }) 24 | 25 | t.Run("should struct types", func(t *testing.T) { 26 | type person struct { 27 | age int 28 | } 29 | input := []person{ 30 | {30}, 31 | {20}, 32 | {40}, 33 | {10}, 34 | } 35 | var output person 36 | 37 | err := godash.Find(input, &output, func(p person) bool { 38 | return p.age > 20 39 | }) 40 | expected := person{30} 41 | 42 | assert.NoError(t, err) 43 | assert.Equal(t, expected, output) 44 | }) 45 | 46 | t.Run("should validate predicate's arg", func(t *testing.T) { 47 | input := []int{1, 2, 3, 4, 5, 6, 7, 8} 48 | var output int 49 | 50 | err := godash.Find(input, &output, func(a string) bool { 51 | return a == "" 52 | }) 53 | 54 | assert.EqualError(t, err, "predicate function's first argument has to be the type (int) instead of (string)") 55 | }) 56 | 57 | t.Run("should validate predicate's return type", func(t *testing.T) { 58 | input := []int{1, 2, 3, 4, 5, 6, 7, 8} 59 | var output int 60 | 61 | { 62 | err := godash.Find(input, &output, func(a int) int { 63 | return a 64 | }) 65 | assert.EqualError(t, err, "predicate function should return only a (boolean) and not a (int)") 66 | } 67 | { 68 | err := godash.Find(input, &output, func(int) (int, bool) { 69 | return 1, true 70 | }) 71 | assert.EqualError(t, err, "predicate function should return only one return value - a boolean") 72 | } 73 | }) 74 | 75 | t.Run("should validate output's type", func(t *testing.T) { 76 | input := []int{1, 2, 3, 4, 5, 6, 7, 8} 77 | var output string 78 | 79 | err := godash.Find(input, &output, func(a int) bool { 80 | return a == 0 81 | }) 82 | 83 | assert.EqualError(t, err, "input slice (int) and output (string) should be of the same Type") 84 | }) 85 | 86 | t.Run("should return error if element not found", func(t *testing.T) { 87 | in := []int{1, 2, 3} 88 | { 89 | var out int 90 | 91 | err := godash.Find(in, &out, func(x int) bool { return x == 4 }) 92 | 93 | assert.EqualError(t, err, "element not found") 94 | } 95 | { 96 | var out int 97 | err := godash.Find(in, &out, func(x int) bool { return x == 1 }) 98 | assert.NoError(t, err) 99 | } 100 | }) 101 | 102 | } 103 | 104 | func ExampleFind() { 105 | input := []string{ 106 | "rhythm", 107 | "of", 108 | "life", 109 | } 110 | var output string 111 | 112 | _ = godash.Find(input, &output, func(in string) bool { 113 | return strings.HasPrefix(in, "r") 114 | }) 115 | fmt.Println(output) 116 | 117 | // Output: 118 | // rhythm 119 | } 120 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/thecasualcoder/godash 2 | 3 | go 1.13 4 | 5 | require github.com/stretchr/testify v1.4.0 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 5 | github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= 6 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 7 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= 8 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 9 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 10 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 11 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= 12 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 13 | -------------------------------------------------------------------------------- /map.go: -------------------------------------------------------------------------------- 1 | package godash 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | // Map applies mapperFn on each item of in and puts it in out. 9 | // Currently, input and output for type slice is supported. 10 | // 11 | // Validations: 12 | // 13 | // 1. Mapper function should take in one argument and return one argument 14 | // 2. Mapper function's argument should be of the same type of each element of input slice. 15 | // 3. Mapper function's output should be of the same type of each element of output slice. 16 | // 17 | // Validation failures are returned as error by the godash.Map to the caller. 18 | func Map(in, out, mapperFn interface{}) error { 19 | input := reflect.ValueOf(in) 20 | output := reflect.ValueOf(out) 21 | if err := validateOut(output); err != nil { 22 | return err 23 | } 24 | 25 | mapper := reflect.ValueOf(mapperFn) 26 | if mapper.Kind() != reflect.Func { 27 | return fmt.Errorf("mapperFn has to be a function") 28 | } 29 | 30 | mapperFnType := mapper.Type() 31 | 32 | if mapperFnType.NumOut() != 1 { 33 | return fmt.Errorf("mapper function should return only one return value") 34 | } 35 | 36 | if input.Kind() == reflect.Slice { 37 | if output.Elem().Kind() != reflect.Slice { 38 | return fmt.Errorf("output should be a slice for input of type slice") 39 | } 40 | 41 | if mapperFnType.NumIn() != 1 { 42 | return fmt.Errorf("mapper function has to take only one argument") 43 | } 44 | 45 | if input.Type().Elem() != mapper.Type().In(0) { 46 | return fmt.Errorf("mapper function's first argument (%s) has to be (%s)", mapper.Type().In(0), input.Type().Elem()) 47 | } 48 | if output.Elem().Type().Elem() != mapper.Type().Out(0) { 49 | return fmt.Errorf("mapper function's return type has to be (%s) but is (%s)", mapper.Type().Out(0), output.Elem().Type().Elem()) 50 | } 51 | 52 | result := reflect.MakeSlice(output.Elem().Type(), 0, input.Len()) 53 | for i := 0; i < input.Len(); i++ { 54 | arg := input.Index(i) 55 | 56 | returnValues := mapper.Call([]reflect.Value{arg}) 57 | 58 | result = reflect.Append(result, returnValues[0]) 59 | } 60 | output.Elem().Set(result) 61 | 62 | return nil 63 | } 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 | } 96 | return fmt.Errorf("not implemented") 97 | } 98 | -------------------------------------------------------------------------------- /map_test.go: -------------------------------------------------------------------------------- 1 | package godash_test 2 | 3 | import ( 4 | "fmt" 5 | "github.com/stretchr/testify/assert" 6 | "github.com/thecasualcoder/godash" 7 | "sort" 8 | "testing" 9 | ) 10 | 11 | func TestMap(t *testing.T) { 12 | t.Run("support primitive types", func(t *testing.T) { 13 | in := []int{1, 2, 3} 14 | out := make([]int, 0) 15 | 16 | err := godash.Map(in, &out, func(element int) int { 17 | return element * element 18 | }) 19 | 20 | expected := []int{1, 4, 9} 21 | assert.NoError(t, err) 22 | assert.Equal(t, expected, out) 23 | }) 24 | 25 | t.Run("support structs", func(t *testing.T) { 26 | type person struct { 27 | name string 28 | } 29 | 30 | in := []person{ 31 | {name: "john"}, 32 | {name: "doe"}, 33 | } 34 | out := make([]string, 0) 35 | expected := []string{"john", "doe"} 36 | 37 | err := godash.Map(in, &out, func(p person) string { 38 | return p.name 39 | }) 40 | 41 | assert.NoError(t, err) 42 | assert.Equal(t, expected, out) 43 | }) 44 | 45 | squared := func(element int) int { 46 | return element * element 47 | } 48 | 49 | t.Run("should not panic if output is nil", func(t *testing.T) { 50 | in := []int{1, 2, 3} 51 | { 52 | var out []int 53 | 54 | err := godash.Map(in, out, squared) 55 | 56 | assert.EqualError(t, err, "output is nil. Pass a reference to set output") 57 | } 58 | 59 | { 60 | err := godash.Map(in, nil, squared) 61 | 62 | assert.EqualError(t, err, "output is nil. Pass a reference to set output") 63 | } 64 | }) 65 | 66 | t.Run("should not panic if output is not a slice", func(t *testing.T) { 67 | in := []int{1, 2, 3} 68 | var out int 69 | 70 | err := godash.Map(in, &out, squared) 71 | 72 | assert.EqualError(t, err, "output should be a slice for input of type slice") 73 | }) 74 | 75 | t.Run("should not accept mapper function that are not functions", func(t *testing.T) { 76 | in := []int{1, 2, 3} 77 | var out []int 78 | 79 | err := godash.Map(in, &out, 7) 80 | 81 | assert.EqualError(t, err, "mapperFn has to be a function") 82 | }) 83 | 84 | t.Run("should not accept mapper function that do not take exactly one argument", func(t *testing.T) { 85 | in := []int{1, 2, 3} 86 | var out []int 87 | 88 | { 89 | err := godash.Map(in, &out, func() int { return 0 }) 90 | assert.EqualError(t, err, "mapper function has to take only one argument") 91 | } 92 | 93 | { 94 | err := godash.Map(in, &out, func(int, int) int { return 0 }) 95 | assert.EqualError(t, err, "mapper function has to take only one argument") 96 | } 97 | }) 98 | 99 | t.Run("should not accept mapper function that do not return exactly one value", func(t *testing.T) { 100 | in := []int{1, 2, 3} 101 | var out []int 102 | 103 | { 104 | err := godash.Map(in, &out, func(int) {}) 105 | assert.EqualError(t, err, "mapper function should return only one return value") 106 | } 107 | 108 | { 109 | err := godash.Map(in, &out, func(int) (int, int) { return 0, 0 }) 110 | assert.EqualError(t, err, "mapper function should return only one return value") 111 | } 112 | }) 113 | 114 | t.Run("should accept mapper function whose argument's kind should be slice's element kind", func(t *testing.T) { 115 | in := []int{1, 2, 3} 116 | var out []int 117 | 118 | { 119 | err := godash.Map(in, &out, func(string) string { return "" }) 120 | assert.EqualError(t, err, "mapper function's first argument (string) has to be (int)") 121 | } 122 | 123 | { 124 | err := godash.Map(in, &out, func(int) int { return 0 }) 125 | assert.NoError(t, err) 126 | } 127 | }) 128 | 129 | t.Run("should accept mapper function whose return's kind should be output slice's element kind", func(t *testing.T) { 130 | in := []int{1, 2, 3} 131 | var out []string 132 | 133 | { 134 | err := godash.Map(in, &out, func(int) int { return 0 }) 135 | assert.EqualError(t, err, "mapper function's return type has to be (int) but is (string)") 136 | } 137 | 138 | { 139 | err := godash.Map(in, &out, func(int) string { return "" }) 140 | assert.NoError(t, err) 141 | } 142 | }) 143 | } 144 | 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 | 303 | func ExampleMap() { 304 | input := []int{0, 1, 2, 3, 4} 305 | var output []string 306 | 307 | _ = godash.Map(input, &output, func(num int) string { 308 | return fmt.Sprintf("%d", num*num) 309 | }) 310 | 311 | fmt.Println(output) 312 | 313 | // Output: [0 1 4 9 16] 314 | } 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 | } 329 | -------------------------------------------------------------------------------- /reduce.go: -------------------------------------------------------------------------------- 1 | package godash 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | // Reduce can accept a reducer and apply the reducer on each element 9 | // of the input slice while providing an accumulator to save the reduce output. 10 | // 11 | // Input of type slice is supported as of now. 12 | // Output is the accumulator. 13 | // ReduceFn is the reducer function. 14 | // 15 | // Whatever ReduceFn returns is fed as accumulator for the next iteration. 16 | // Reduction happens from left-to-right. 17 | // 18 | // Reduce does the following validations: 19 | // 20 | // 1. Reducer function should accept exactly 2 arguments and return 1 argument 21 | // 2. Reducer function's second argument should be the same type as input slice's element type 22 | // 3. Reducer function's return type should be the same as that of the accumulator 23 | // 24 | // Validation errors are returned to the caller. 25 | func Reduce(in, out, reduceFn interface{}) error { 26 | input := reflect.ValueOf(in) 27 | output := reflect.ValueOf(out) 28 | if err := isReferenceType(output); err != nil { 29 | return err 30 | } 31 | 32 | reducer := reflect.ValueOf(reduceFn) 33 | if err := validateReducer(reducer); err != nil { 34 | return err 35 | } 36 | 37 | if input.Kind() == reflect.Slice { 38 | outputKind := output.Elem().Kind() 39 | reducerFnType := reducer.Type() 40 | if outputKind != reducerFnType.In(0).Kind() { 41 | return fmt.Errorf("reduceFn's first argument's type(%s) has to be the type of out(%s)", reducerFnType.In(0).Kind(), outputKind) 42 | } 43 | if input.Type().Elem().Kind() != reducerFnType.In(1).Kind() { 44 | 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()) 45 | } 46 | if outputKind != reducerFnType.Out(0).Kind() { 47 | return fmt.Errorf("reduceFn's return type(%s) has to be the type of out(%s)", reducerFnType.Out(0).Kind(), outputKind) 48 | } 49 | 50 | result := output.Elem() 51 | for i := 0; i < input.Len(); i++ { 52 | arg := input.Index(i) 53 | returnValues := reducer.Call([]reflect.Value{result, arg}) 54 | 55 | result = returnValues[0] 56 | } 57 | output.Elem().Set(result) 58 | 59 | return nil 60 | } 61 | return fmt.Errorf("not implemented") 62 | } 63 | 64 | func validateReducer(reducer reflect.Value) error { 65 | reducerFnType := reducer.Type() 66 | if reducer.Kind() != reflect.Func { 67 | return fmt.Errorf("reduceFn has to be a (func) and not (%s)", reducer.Kind()) 68 | } 69 | if reducerFnType.NumIn() != 2 { 70 | return fmt.Errorf("reduceFn has to take exactly 2 arguments and not %d argument(s)", reducerFnType.NumIn()) 71 | } 72 | if reducerFnType.NumOut() != 1 { 73 | return fmt.Errorf("reduceFn should have only one return value and not %d return type(s)", reducerFnType.NumOut()) 74 | } 75 | return nil 76 | } 77 | 78 | func isReferenceType(output reflect.Value) error { 79 | zeroValue := reflect.Value{} 80 | if output == zeroValue { 81 | return fmt.Errorf("output is nil. Pass a reference to set output") 82 | } 83 | if output.Kind() != reflect.Ptr { 84 | return fmt.Errorf("cannot set out. Pass a reference to set output") 85 | } 86 | return nil 87 | } 88 | -------------------------------------------------------------------------------- /reduce_test.go: -------------------------------------------------------------------------------- 1 | package godash_test 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "github.com/stretchr/testify/assert" 7 | "github.com/thecasualcoder/godash" 8 | "strconv" 9 | "testing" 10 | ) 11 | 12 | func TestReduce(t *testing.T) { 13 | t.Run("support primitive types", func(t *testing.T) { 14 | { 15 | in := []int{1, 2, 3} 16 | var out int 17 | 18 | err := godash.Reduce(in, &out, func(acc, element int) int { 19 | return acc + element 20 | }) 21 | 22 | expected := 6 23 | assert.NoError(t, err) 24 | assert.Equal(t, expected, out) 25 | } 26 | { 27 | in := []int{1, 2, 3} 28 | var out string 29 | 30 | err := godash.Reduce(in, &out, func(acc string, element int) string { 31 | return acc + strconv.Itoa(element) 32 | }) 33 | 34 | expected := "123" 35 | assert.NoError(t, err) 36 | assert.Equal(t, expected, out) 37 | } 38 | { 39 | in := []string{"one", "two", "two", "three", "three", "three"} 40 | out := map[string]int{} 41 | 42 | err := godash.Reduce(in, &out, func(acc map[string]int, element string) map[string]int { 43 | if _, present := acc[element]; present { 44 | acc[element] = acc[element] + 1 45 | } else { 46 | acc[element] = 1 47 | } 48 | return acc 49 | }) 50 | 51 | expected := map[string]int{"one": 1, "two": 2, "three": 3} 52 | assert.NoError(t, err) 53 | assert.Equal(t, expected, out) 54 | } 55 | }) 56 | 57 | t.Run("support structs", func(t *testing.T) { 58 | type person struct { 59 | age int 60 | } 61 | 62 | in := []person{ 63 | {age: 20}, 64 | {age: 23}, 65 | } 66 | out := 0 67 | expected := 43 68 | 69 | err := godash.Reduce(in, &out, func(acc int, p person) int { 70 | return acc + p.age 71 | }) 72 | 73 | assert.NoError(t, err) 74 | assert.Equal(t, expected, out) 75 | }) 76 | 77 | add := func(acc, element int) int { 78 | return acc + element 79 | } 80 | 81 | t.Run("should not panic if output is nil", func(t *testing.T) { 82 | in := []int{1, 2, 3} 83 | { 84 | var out int 85 | 86 | err := godash.Reduce(in, out, add) 87 | 88 | assert.EqualError(t, err, "cannot set out. Pass a reference to set output") 89 | } 90 | 91 | { 92 | err := godash.Reduce(in, nil, add) 93 | 94 | assert.EqualError(t, err, "output is nil. Pass a reference to set output") 95 | } 96 | }) 97 | 98 | t.Run("should not accept reducer function that are not functions", func(t *testing.T) { 99 | in := []int{1, 2, 3} 100 | var out int 101 | 102 | err := godash.Reduce(in, &out, 7) 103 | 104 | assert.EqualError(t, err, "reduceFn has to be a (func) and not (int)") 105 | }) 106 | 107 | t.Run("should not accept reducer function that do not take exactly two argument", func(t *testing.T) { 108 | in := []int{1, 2, 3} 109 | var out int 110 | 111 | { 112 | err := godash.Reduce(in, &out, func() int { return 0 }) 113 | assert.EqualError(t, err, "reduceFn has to take exactly 2 arguments and not 0 argument(s)") 114 | } 115 | 116 | { 117 | err := godash.Reduce(in, &out, func(int) int { return 0 }) 118 | assert.EqualError(t, err, "reduceFn has to take exactly 2 arguments and not 1 argument(s)") 119 | } 120 | }) 121 | 122 | t.Run("should not accept reducer function that do not return exactly one value", func(t *testing.T) { 123 | in := []int{1, 2, 3} 124 | var out int 125 | 126 | { 127 | err := godash.Reduce(in, &out, func(int, int) {}) 128 | assert.EqualError(t, err, "reduceFn should have only one return value and not 0 return type(s)") 129 | } 130 | 131 | { 132 | err := godash.Reduce(in, &out, func(int, int) (int, int) { return 0, 0 }) 133 | assert.EqualError(t, err, "reduceFn should have only one return value and not 2 return type(s)") 134 | } 135 | }) 136 | 137 | t.Run("should accept reducer function whose first argument's kind should be output's kind", func(t *testing.T) { 138 | in := []int{1, 2, 3} 139 | var out int 140 | 141 | { 142 | err := godash.Reduce(in, &out, func(string, int) int { return 0 }) 143 | assert.EqualError(t, err, "reduceFn's first argument's type(string) has to be the type of out(int)") 144 | } 145 | 146 | { 147 | err := godash.Reduce(in, &out, func(int, int) int { return 0 }) 148 | assert.NoError(t, err) 149 | } 150 | }) 151 | 152 | t.Run("should accept reducer function whose second argument's kind should be input slice's element kind", func(t *testing.T) { 153 | in := []int{1, 2, 3} 154 | var out string 155 | 156 | { 157 | err := godash.Reduce(in, &out, func(string, string) string { return "" }) 158 | assert.EqualError(t, err, "reduceFn's second argument's type(string) has to be the type of element of input slice(int)") 159 | } 160 | 161 | { 162 | err := godash.Reduce(in, &out, func(string, int) string { return "" }) 163 | assert.NoError(t, err) 164 | } 165 | }) 166 | 167 | t.Run("should accept reducer function whose return kind should be output's kind", func(t *testing.T) { 168 | in := []int{1, 2, 3} 169 | var out string 170 | 171 | { 172 | err := godash.Reduce(in, &out, func(string, int) int { return 0 }) 173 | assert.EqualError(t, err, "reduceFn's return type(int) has to be the type of out(string)") 174 | } 175 | 176 | { 177 | err := godash.Reduce(in, &out, func(string, int) string { return "" }) 178 | assert.NoError(t, err) 179 | } 180 | }) 181 | } 182 | 183 | func ExampleReduce() { 184 | input := []string{"count", "words", "and", "print", "words", "count"} 185 | accumulator := map[string]int{} 186 | 187 | _ = godash.Reduce(input, &accumulator, func(acc map[string]int, element string) map[string]int { 188 | if _, present := acc[element]; present { 189 | acc[element] = acc[element] + 1 190 | } else { 191 | acc[element] = 1 192 | } 193 | return acc 194 | }) 195 | 196 | bytes, _ := json.MarshalIndent(accumulator, "", " ") 197 | fmt.Println(string(bytes)) 198 | 199 | // Output: 200 | //{ 201 | // "and": 1, 202 | // "count": 2, 203 | // "print": 1, 204 | // "words": 2 205 | //} 206 | } 207 | --------------------------------------------------------------------------------