├── .github └── workflows │ ├── go.yml │ └── golangci-lint.yml ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── README.rst ├── assign.go ├── assign_test.go ├── benchmark_test.go ├── builder.go ├── builder_test.go ├── chain_builder.go ├── chain_builder_test.go ├── compact.go ├── compact_test.go ├── example_presence_test.go ├── fill.go ├── fill_test.go ├── funk_test.go ├── go.mod ├── go.sum ├── helpers.go ├── helpers_test.go ├── intersection.go ├── intersection_test.go ├── join.go ├── join_primitives.go ├── join_test.go ├── lazy_builder.go ├── lazy_builder_test.go ├── map.go ├── map_test.go ├── max.go ├── max_test.go ├── min.go ├── min_test.go ├── operation.go ├── operation_test.go ├── options.go ├── permutation.go ├── permutation_test.go ├── predicate.go ├── predicate_test.go ├── presence.go ├── presence_test.go ├── reduce.go ├── reduce_test.go ├── retrieve.go ├── retrieve_test.go ├── scan.go ├── scan_test.go ├── short_if.go ├── short_if_test.go ├── subset.go ├── subset_test.go ├── subtraction.go ├── subtraction_test.go ├── transform.go ├── transform_test.go ├── typesafe.go ├── typesafe_test.go ├── union.go ├── union_test.go ├── utils.go ├── utils_test.go ├── without.go ├── without_test.go ├── zip.go └── zip_test.go /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | 15 | - name: Set up Go 16 | uses: actions/setup-go@v2 17 | with: 18 | go-version: 1.19 19 | 20 | - name: Build 21 | run: go build -v ./... 22 | 23 | - name: Test 24 | run: make test 25 | -------------------------------------------------------------------------------- /.github/workflows/golangci-lint.yml: -------------------------------------------------------------------------------- 1 | name: golangci-lint 2 | on: 3 | push: 4 | tags: 5 | - v* 6 | branches: 7 | - master 8 | - main 9 | pull_request: 10 | jobs: 11 | golangci: 12 | name: lint 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: golangci-lint 17 | uses: golangci/golangci-lint-action@v2 18 | with: 19 | version: v1.52.2 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | 26 | #GoLand 27 | .idea -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | before_install: 3 | - go get golang.org/x/tools/cmd/cover 4 | - go get github.com/stretchr/testify 5 | go: 6 | - "1.16" 7 | script: make test 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | go-funk changelog 2 | ================= 3 | 4 | 0.1 (2017-01-18) 5 | ---------------- 6 | 7 | Changes can be seen [here](https://github.com/thoas/go-funk/compare/73b8ae1f6443c9d4acbdc612bbb2ca804bb39b1d...master) 8 | 9 | * Better test suite 10 | * Better documentation 11 | * Add typesafe implementations: 12 | 13 | * ``Contains`` 14 | * ``Sum`` 15 | * ``Reverse`` 16 | * ``IndexOf`` 17 | * ``Uniq`` 18 | * ``Shuffle`` 19 | * Add benchmarks 20 | 21 | * ``Contains`` 22 | * ``Uniq`` 23 | * ``Sum`` 24 | * Fix ``redirectValue`` when using a circular reference 25 | * Add ``Sum`` generic implementation which computes the sum of values in an array 26 | * Add ``Tail`` generic implementation to retrieve all but the first element of array 27 | * Add ``Initial`` generic implementation to retrieve all but the last element of array 28 | * Add ``Last`` generic implementation to retrieve the last element of an array 29 | * Add ``Head`` generic implementation to retrieve the first element of an array 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Florent Messa 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 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | build: 2 | go build -v ./... 3 | 4 | test: 5 | go test -v ./... 6 | 7 | lint: 8 | golangci-lint run 9 | 10 | bench: 11 | go test -benchmem -bench . 12 | -------------------------------------------------------------------------------- /assign.go: -------------------------------------------------------------------------------- 1 | package funk 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "reflect" 7 | "strings" 8 | ) 9 | 10 | // Set assigns in at path with value val. i.e. in.path = val 11 | // in accepts types of ptr to struct, ptr to variable, slice and ptr to slice. 12 | // Along the path, interface{} is supported and nil ptr is initialized to ptr to zero value 13 | // of the type until the variable to be set is obtained. 14 | // It returns errors when encountering along the path unknown types, uninitialized 15 | // interface{} or interface{} containing struct directly (not ptr to struct). 16 | // 17 | // Slice is resolved the same way in funk.Get(), by traversing each element of the slice, 18 | // so that each element of the slice's corresponding field are going to be set to the same provided val. 19 | // If Set is called on slice with empty path "", it behaves the same as funk.Fill() 20 | // 21 | // If in is well formed, i.e. do not expect above descripted errors to happen, funk.MustSet() 22 | // is a short hand wrapper to discard error return 23 | func Set(in interface{}, val interface{}, path string) error { 24 | if in == nil { 25 | return errors.New("cannot set nil") 26 | } 27 | parts := []string{} 28 | if path != "" { 29 | parts = strings.Split(path, ".") 30 | } 31 | return setByParts(in, val, parts) 32 | } 33 | 34 | // we need this layer to handle interface{} type 35 | func setByParts(in interface{}, val interface{}, parts []string) error { 36 | 37 | if in == nil { 38 | // nil interface can happen during traversing the path 39 | return errors.New("cannot traverse nil/uninitialized interface{}") 40 | } 41 | 42 | inValue := reflect.ValueOf(in) 43 | inKind := inValue.Type().Kind() 44 | 45 | // Note: if interface contains a struct (not ptr to struct) then the content of the struct cannot be set. 46 | // I.e. it is not CanAddr() or CanSet() 47 | // So we require in interface{} to be a ptr, slice or array 48 | if inKind == reflect.Ptr { 49 | inValue = inValue.Elem() // if it is ptr we set its content not ptr its self 50 | } else if inKind != reflect.Array && inKind != reflect.Slice { 51 | return fmt.Errorf("Type %s not supported by Set", inValue.Type().String()) 52 | } 53 | 54 | return set(inValue, reflect.ValueOf(val), parts) 55 | } 56 | 57 | // traverse inValue using path in parts and set the dst to be setValue 58 | func set(inValue reflect.Value, setValue reflect.Value, parts []string) error { 59 | 60 | // traverse the path to get the inValue we need to set 61 | i := 0 62 | for i < len(parts) { 63 | 64 | kind := inValue.Kind() 65 | 66 | switch kind { 67 | case reflect.Invalid: 68 | // do not expect this case to happen 69 | return errors.New("nil pointer found along the path") 70 | case reflect.Struct: 71 | fValue := inValue.FieldByName(parts[i]) 72 | if !fValue.IsValid() { 73 | return fmt.Errorf("field name %v is not found in struct %v", parts[i], inValue.Type().String()) 74 | } 75 | if !fValue.CanSet() { 76 | return fmt.Errorf("field name %v is not exported in struct %v", parts[i], inValue.Type().String()) 77 | } 78 | inValue = fValue 79 | i++ 80 | case reflect.Slice, reflect.Array: 81 | // set all its elements 82 | length := inValue.Len() 83 | for j := 0; j < length; j++ { 84 | err := set(inValue.Index(j), setValue, parts[i:]) 85 | if err != nil { 86 | return err 87 | } 88 | } 89 | return nil 90 | case reflect.Ptr: 91 | // only traverse down one level 92 | if inValue.IsNil() { 93 | // we initialize nil ptr to ptr to zero value of the type 94 | // and continue traversing 95 | inValue.Set(reflect.New(inValue.Type().Elem())) 96 | } 97 | // traverse the ptr until it is not pointer any more or is nil again 98 | inValue = redirectValue(inValue) 99 | case reflect.Interface: 100 | // Note: if interface contains a struct (not ptr to struct) then the content of the struct cannot be set. 101 | // I.e. it is not CanAddr() or CanSet(). This is why setByParts has a nil ptr check. 102 | // we treat this as a new call to setByParts, and it will do proper check of the types 103 | return setByParts(inValue.Interface(), setValue.Interface(), parts[i:]) 104 | default: 105 | return fmt.Errorf("kind %v in path %v is not supported", kind, parts[i]) 106 | } 107 | 108 | } 109 | // here inValue holds the value we need to set 110 | 111 | // interface{} can be set to any val 112 | // other types we ensure the type matches 113 | if inValue.Kind() != setValue.Kind() && inValue.Kind() != reflect.Interface { 114 | return fmt.Errorf("cannot set target of type %v with type %v", inValue.Kind(), setValue.Kind()) 115 | } 116 | inValue.Set(setValue) 117 | 118 | return nil 119 | } 120 | 121 | // MustSet is functionally the same as Set. 122 | // It panics instead of returning error. 123 | // It is safe to use if the in value is well formed. 124 | func MustSet(in interface{}, val interface{}, path string) { 125 | err := Set(in, val, path) 126 | if err != nil { 127 | panic(err) 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /assign_test.go: -------------------------------------------------------------------------------- 1 | package funk 2 | 3 | import ( 4 | "database/sql" 5 | "fmt" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestSet_EmptyPath(t *testing.T) { 12 | // it is supposed to change the var passed in 13 | var testCases = []struct { 14 | // will use path = "" 15 | Original interface{} 16 | SetVal interface{} 17 | }{ 18 | // int 19 | { 20 | Original: 100, 21 | SetVal: 1, 22 | }, 23 | // string 24 | { 25 | Original: "", 26 | SetVal: "val", 27 | }, 28 | // struct 29 | { 30 | Original: Bar{Name: "bar"}, 31 | SetVal: Bar{Name: "val"}, 32 | }, 33 | // slice 34 | { 35 | Original: []Bar{{Name: "bar"}}, 36 | SetVal: []Bar{{Name: "val1"}, {Name: "val2"}}, 37 | }, 38 | } 39 | 40 | for idx, tc := range testCases { 41 | t.Run(fmt.Sprintf("test case #%d", idx+1), func(t *testing.T) { 42 | is := assert.New(t) 43 | // use empty path 44 | // must take the addr of the variable to be set 45 | err := Set(&tc.Original, tc.SetVal, "") 46 | is.NoError(err) 47 | is.Equal(tc.Original, tc.SetVal) // original should be set to SetVal 48 | }) 49 | } 50 | } 51 | 52 | func TestSet_StructBasicOneLevel(t *testing.T) { 53 | is := assert.New(t) 54 | // we set field one by one of baz with expected value 55 | baz := Foo{ 56 | ID: 100, 57 | FirstName: "firstname", 58 | LastName: "lastname", 59 | Age: 23, 60 | Bar: &Bar{Name: "bar"}, 61 | Bars: []*Bar{{Name: "1"}}, 62 | EmptyValue: sql.NullInt64{ 63 | Int64: 64, 64 | Valid: false, 65 | }, 66 | } 67 | expected := Foo{ 68 | ID: 1, 69 | FirstName: "firstname1", 70 | LastName: "lastname1", 71 | Age: 24, 72 | Bar: &Bar{Name: "b1", Bar: &Bar{Name: "b2"}}, 73 | Bars: []*Bar{{Name: "1"}, {Name: "2"}}, 74 | EmptyValue: sql.NullInt64{ 75 | Int64: 11, 76 | Valid: true, 77 | }, 78 | } 79 | err := Set(&baz, 1, "ID") 80 | is.NoError(err) 81 | err = Set(&baz, expected.FirstName, "FirstName") 82 | is.NoError(err) 83 | err = Set(&baz, expected.LastName, "LastName") 84 | is.NoError(err) 85 | err = Set(&baz, expected.Age, "Age") 86 | is.NoError(err) 87 | err = Set(&baz, expected.Bar, "Bar") 88 | is.NoError(err) 89 | err = Set(&baz, expected.Bars, "Bars") 90 | is.NoError(err) 91 | err = Set(&baz, expected.EmptyValue, "EmptyValue") 92 | is.NoError(err) 93 | is.Equal(baz, expected) 94 | } 95 | 96 | func TestSetStruct_MultiLevels(t *testing.T) { 97 | 98 | var testCases = []struct { 99 | Original Bar 100 | Path string 101 | SetVal interface{} 102 | Expected Bar 103 | }{ 104 | // Set slice in 4th level 105 | { 106 | Original: Bar{ 107 | Name: "1", // name indicates level 108 | Bar: &Bar{ 109 | Name: "2", 110 | Bars: []*Bar{ 111 | {Name: "3-1", Bars: []*Bar{{Name: "4-1"}, {Name: "4-2"}, {Name: "4-3"}}}, 112 | {Name: "3-2", Bars: []*Bar{{Name: "4-1"}, {Name: "4-2"}}}, 113 | }, 114 | }, 115 | }, 116 | Path: "Bar.Bars.Bars.Name", 117 | SetVal: "val", 118 | Expected: Bar{ 119 | Name: "1", 120 | Bar: &Bar{ 121 | Name: "2", 122 | Bars: []*Bar{ 123 | {Name: "3-1", Bars: []*Bar{{Name: "val"}, {Name: "val"}, {Name: "val"}}}, 124 | {Name: "3-2", Bars: []*Bar{{Name: "val"}, {Name: "val"}}}, 125 | }, 126 | }, 127 | }, 128 | }, 129 | // Set multilevel uninitialized ptr 130 | { 131 | Original: Bar{ 132 | Name: "1", // name indicates level 133 | Bar: nil, 134 | }, 135 | Path: "Bar.Bar.Bar.Name", 136 | SetVal: "val", 137 | Expected: Bar{ 138 | Name: "1", 139 | Bar: &Bar{ 140 | Name: "", // level 2 141 | Bar: &Bar{ 142 | Bar: &Bar{ 143 | Name: "val", //level 3 144 | }, 145 | }, 146 | }, 147 | }, 148 | }, 149 | // mix of uninitialized ptr and slices 150 | { 151 | Original: Bar{ 152 | Name: "1", // name indicates level 153 | Bar: &Bar{ 154 | Name: "2", 155 | Bars: []*Bar{ 156 | {Name: "3-1", Bars: []*Bar{{Name: "4-1"}, {Name: "4-2"}, {Name: "4-3"}}}, 157 | {Name: "3-2", Bars: []*Bar{{Name: "4-1"}, {Name: "4-2"}}}, 158 | }, 159 | }, 160 | }, 161 | Path: "Bar.Bars.Bars.Bar.Name", 162 | SetVal: "val", 163 | Expected: Bar{ 164 | Name: "1", // name indicates level 165 | Bar: &Bar{ 166 | Name: "2", 167 | Bars: []*Bar{ 168 | {Name: "3-1", Bars: []*Bar{{Name: "4-1", Bar: &Bar{Name: "val"}}, 169 | {Name: "4-2", Bar: &Bar{Name: "val"}}, {Name: "4-3", Bar: &Bar{Name: "val"}}}}, 170 | {Name: "3-2", Bars: []*Bar{{Name: "4-1", Bar: &Bar{Name: "val"}}, {Name: "4-2", Bar: &Bar{Name: "val"}}}}, 171 | }, 172 | }, 173 | }, 174 | }, 175 | } 176 | 177 | for idx, tc := range testCases { 178 | t.Run(fmt.Sprintf("test case #%d", idx+1), func(t *testing.T) { 179 | is := assert.New(t) 180 | // take the addr and then pass it in 181 | err := Set(&tc.Original, tc.SetVal, tc.Path) 182 | is.NoError(err) 183 | is.Equal(tc.Expected, tc.Original) 184 | }) 185 | } 186 | } 187 | 188 | func TestSet_StructWithCyclicStruct(t *testing.T) { 189 | is := assert.New(t) 190 | 191 | testBar := Bar{ 192 | Name: "testBar", 193 | Bar: nil, 194 | } 195 | testBar.Bar = &testBar 196 | 197 | err := Set(&testBar, "val", "Bar.Bar.Name") 198 | is.NoError(err) 199 | is.Equal("val", testBar.Name) 200 | } 201 | 202 | func TestSet_StructWithFieldNotInitialized(t *testing.T) { 203 | is := assert.New(t) 204 | myFoo := &Foo{ 205 | Bar: nil, // we will try to set bar's field 206 | } 207 | err := Set(myFoo, "name", "Bar.Name") 208 | is.NoError(err) 209 | is.Equal("name", myFoo.Bar.Name) 210 | } 211 | 212 | func TestSet_SlicePassByPtr(t *testing.T) { 213 | 214 | var testCases = []struct { 215 | Original interface{} // slice or array 216 | Path string 217 | SetVal interface{} 218 | Expected interface{} 219 | }{ 220 | // Set Slice itself 221 | { 222 | Original: []*Bar{}, 223 | Path: "", // empty path means set the passed in ptr itself 224 | SetVal: []*Bar{{Name: "bar"}}, 225 | Expected: []*Bar{{Name: "bar"}}, 226 | }, 227 | // empty slice 228 | { 229 | Original: []*Bar{}, 230 | Path: "Name", 231 | SetVal: "val", 232 | Expected: []*Bar{}, 233 | }, 234 | // slice of ptr 235 | { 236 | Original: []*Bar{{Name: "a"}, {Name: "b"}}, 237 | Path: "Name", 238 | SetVal: "val", 239 | Expected: []*Bar{{Name: "val"}, {Name: "val"}}, 240 | }, 241 | // slice of struct 242 | { 243 | Original: []Bar{{Name: "a"}, {Name: "b"}}, 244 | Path: "Name", 245 | SetVal: "val", 246 | Expected: []Bar{{Name: "val"}, {Name: "val"}}, 247 | }, 248 | // slice of empty ptr 249 | { 250 | Original: []*Bar{nil, nil}, 251 | Path: "Name", 252 | SetVal: "val", 253 | Expected: []*Bar{{Name: "val"}, {Name: "val"}}, 254 | }, 255 | // mix of init ptr and nil ptr 256 | { 257 | Original: []*Bar{{Name: "bar"}, nil}, 258 | Path: "Name", 259 | SetVal: "val", 260 | Expected: []*Bar{{Name: "val"}, {Name: "val"}}, 261 | }, 262 | } 263 | 264 | for idx, tc := range testCases { 265 | t.Run(fmt.Sprintf("test case #%d", idx+1), func(t *testing.T) { 266 | is := assert.New(t) 267 | // take the addr and then pass it in 268 | err := Set(&tc.Original, tc.SetVal, tc.Path) 269 | is.NoError(err) 270 | is.Equal(tc.Expected, tc.Original) 271 | }) 272 | } 273 | } 274 | 275 | func TestSet_SlicePassDirectly(t *testing.T) { 276 | var testCases = []struct { 277 | Original interface{} // slice or array 278 | Path string 279 | SetVal interface{} 280 | Expected interface{} 281 | }{ 282 | // Set Slice itself does not work here since not passing by ptr 283 | 284 | // empty slice 285 | { 286 | Original: []*Bar{}, 287 | Path: "Name", 288 | SetVal: "val", 289 | Expected: []*Bar{}, 290 | }, 291 | // slice of ptr 292 | { 293 | Original: []*Bar{{Name: "a"}, {Name: "b"}}, 294 | Path: "Name", 295 | SetVal: "val", 296 | Expected: []*Bar{{Name: "val"}, {Name: "val"}}, 297 | }, 298 | // Array of ptr 299 | { 300 | Original: [2]*Bar{{Name: "a"}, {Name: "b"}}, 301 | Path: "Name", 302 | SetVal: "val", 303 | Expected: [2]*Bar{{Name: "val"}, {Name: "val"}}, 304 | }, 305 | // slice of struct 306 | { 307 | Original: []Bar{{Name: "a"}, {Name: "b"}}, 308 | Path: "Name", 309 | SetVal: "val", 310 | Expected: []Bar{{Name: "val"}, {Name: "val"}}, 311 | }, 312 | // slice of empty ptr 313 | { 314 | Original: []*Bar{nil, nil}, 315 | Path: "Name", 316 | SetVal: "val", 317 | Expected: []*Bar{{Name: "val"}, {Name: "val"}}, 318 | }, 319 | // mix of init ptr and nil ptr 320 | { 321 | Original: []*Bar{{Name: "bar"}, nil}, 322 | Path: "Name", 323 | SetVal: "val", 324 | Expected: []*Bar{{Name: "val"}, {Name: "val"}}, 325 | }, 326 | } 327 | 328 | for idx, tc := range testCases { 329 | t.Run(fmt.Sprintf("test case #%d", idx+1), func(t *testing.T) { 330 | is := assert.New(t) 331 | // Not take ptr, pass directly 332 | err := Set(tc.Original, tc.SetVal, tc.Path) 333 | is.NoError(err) 334 | is.Equal(tc.Expected, tc.Original) 335 | }) 336 | } 337 | } 338 | 339 | func TestInterface(t *testing.T) { 340 | 341 | var testCases = []struct { 342 | OriginalFoo Foo 343 | Path string 344 | SetVal interface{} 345 | ExpectedFoo Foo 346 | }{ 347 | // set string field 348 | { 349 | Foo{FirstName: ""}, 350 | "FirstName", 351 | "hi", 352 | Foo{FirstName: "hi"}, 353 | }, 354 | // set interface{} field 355 | { 356 | Foo{FirstName: "", GeneralInterface: nil}, 357 | "GeneralInterface", 358 | "str", 359 | Foo{FirstName: "", GeneralInterface: "str"}, 360 | }, 361 | // set field of the interface{} field 362 | // Note: set uninitialized interface{} should fail 363 | // Note: interface of struct (not ptr to struct) should fail 364 | { 365 | Foo{FirstName: "", GeneralInterface: &Foo{FirstName: ""}}, // if Foo is not ptr this will fail 366 | "GeneralInterface.FirstName", 367 | "foo", 368 | Foo{FirstName: "", GeneralInterface: &Foo{FirstName: "foo"}}, 369 | }, 370 | // interface two level 371 | { 372 | Foo{FirstName: "", GeneralInterface: &Foo{GeneralInterface: nil}}, 373 | "GeneralInterface.GeneralInterface", 374 | "val", 375 | Foo{FirstName: "", GeneralInterface: &Foo{GeneralInterface: "val"}}, 376 | }, 377 | } 378 | 379 | for idx, tc := range testCases { 380 | t.Run(fmt.Sprintf("test case #%d", idx+1), func(t *testing.T) { 381 | is := assert.New(t) 382 | 383 | err := Set(&tc.OriginalFoo, tc.SetVal, tc.Path) 384 | is.NoError(err) 385 | is.Equal(tc.ExpectedFoo, tc.OriginalFoo) 386 | }) 387 | } 388 | 389 | } 390 | 391 | func TestSet_ErrorCaces(t *testing.T) { 392 | 393 | var testCases = []struct { 394 | OriginalFoo Foo 395 | Path string 396 | SetVal interface{} 397 | }{ 398 | // uninit interface 399 | // Itf is not initialized so Set cannot properly allocate type 400 | { 401 | Foo{BarInterface: nil}, 402 | "BarInterface.Name", 403 | "val", 404 | }, 405 | { 406 | Foo{GeneralInterface: &Foo{BarInterface: nil}}, 407 | "GeneralInterface.BarInterface.Name", 408 | "val", 409 | }, 410 | // type mismatch 411 | { 412 | Foo{FirstName: ""}, 413 | "FirstName", 414 | 20, 415 | }, 416 | } 417 | 418 | for idx, tc := range testCases { 419 | t.Run(fmt.Sprintf("test case #%d", idx+1), func(t *testing.T) { 420 | is := assert.New(t) 421 | 422 | err := Set(&tc.OriginalFoo, tc.SetVal, tc.Path) 423 | is.Error(err) 424 | }) 425 | } 426 | 427 | t.Run("not pointer", func(t *testing.T) { 428 | is := assert.New(t) 429 | baz := Bar{Name: "dummy"} 430 | err := Set(baz, Bar{Name: "dummy2"}, "Name") 431 | is.Error(err) 432 | }) 433 | 434 | t.Run("Unexported field", func(t *testing.T) { 435 | is := assert.New(t) 436 | s := struct { 437 | name string 438 | }{name: "dummy"} 439 | err := Set(&s, s, "name") 440 | is.Error(err) 441 | }) 442 | } 443 | 444 | func TestMustSet_Basic(t *testing.T) { 445 | t.Run("Variable", func(t *testing.T) { 446 | is := assert.New(t) 447 | s := 1 448 | MustSet(&s, 2, "") 449 | is.Equal(2, s) 450 | }) 451 | 452 | t.Run("Struct", func(t *testing.T) { 453 | is := assert.New(t) 454 | s := Bar{Name: "a"} 455 | MustSet(&s, "b", "Name") 456 | is.Equal("b", s.Name) 457 | }) 458 | } 459 | 460 | // Examples 461 | 462 | func ExampleSet() { 463 | 464 | var bar Bar = Bar{ 465 | Name: "level-0", 466 | Bar: &Bar{ 467 | Name: "level-1", 468 | Bars: []*Bar{ 469 | {Name: "level2-1"}, 470 | {Name: "level2-2"}, 471 | }, 472 | }, 473 | } 474 | 475 | _ = Set(&bar, "level-0-new", "Name") 476 | fmt.Println(bar.Name) 477 | 478 | // discard error use MustSet 479 | MustSet(&bar, "level-1-new", "Bar.Name") 480 | fmt.Println(bar.Bar.Name) 481 | 482 | _ = Set(&bar, "level-2-new", "Bar.Bars.Name") 483 | fmt.Println(bar.Bar.Bars[0].Name) 484 | fmt.Println(bar.Bar.Bars[1].Name) 485 | 486 | // Output: 487 | // level-0-new 488 | // level-1-new 489 | // level-2-new 490 | // level-2-new 491 | } 492 | -------------------------------------------------------------------------------- /benchmark_test.go: -------------------------------------------------------------------------------- 1 | package funk 2 | 3 | import ( 4 | "math/rand" 5 | "testing" 6 | ) 7 | 8 | const ( 9 | seed = 918234565 10 | sliceSize = 3614562 11 | ) 12 | 13 | func sliceGenerator(size uint, r *rand.Rand) (out []int64) { 14 | for i := uint(0); i < size; i++ { 15 | out = append(out, rand.Int63()) 16 | } 17 | return 18 | } 19 | 20 | func BenchmarkSubtract(b *testing.B) { 21 | r := rand.New(rand.NewSource(seed)) 22 | testData := sliceGenerator(sliceSize, r) 23 | what := sliceGenerator(sliceSize, r) 24 | 25 | b.Run("Subtract", func(b *testing.B) { 26 | for n := 0; n < b.N; n++ { 27 | Subtract(testData, what) 28 | } 29 | }) 30 | } 31 | 32 | func BenchmarkContains(b *testing.B) { 33 | r := rand.New(rand.NewSource(seed)) 34 | testData := sliceGenerator(sliceSize, r) 35 | what := r.Int63() 36 | 37 | b.Run("ContainsInt64", func(b *testing.B) { 38 | for n := 0; n < b.N; n++ { 39 | ContainsInt64(testData, what) 40 | } 41 | }) 42 | 43 | b.Run("IndexOfInt64", func(b *testing.B) { 44 | for n := 0; n < b.N; n++ { 45 | IndexOfInt64(testData, what) 46 | } 47 | }) 48 | 49 | b.Run("Contains", func(b *testing.B) { 50 | for n := 0; n < b.N; n++ { 51 | Contains(testData, what) 52 | } 53 | }) 54 | } 55 | 56 | func BenchmarkUniq(b *testing.B) { 57 | r := rand.New(rand.NewSource(seed)) 58 | testData := sliceGenerator(sliceSize, r) 59 | 60 | b.Run("UniqInt64", func(b *testing.B) { 61 | for n := 0; n < b.N; n++ { 62 | UniqInt64(testData) 63 | } 64 | }) 65 | 66 | b.Run("Uniq", func(b *testing.B) { 67 | for n := 0; n < b.N; n++ { 68 | Uniq(testData) 69 | } 70 | }) 71 | } 72 | 73 | func BenchmarkSum(b *testing.B) { 74 | r := rand.New(rand.NewSource(seed)) 75 | testData := sliceGenerator(sliceSize, r) 76 | 77 | b.Run("SumInt64", func(b *testing.B) { 78 | for n := 0; n < b.N; n++ { 79 | SumInt64(testData) 80 | } 81 | }) 82 | 83 | b.Run("Sum", func(b *testing.B) { 84 | for n := 0; n < b.N; n++ { 85 | Sum(testData) 86 | } 87 | }) 88 | } 89 | 90 | func BenchmarkDrop(b *testing.B) { 91 | r := rand.New(rand.NewSource(seed)) 92 | testData := sliceGenerator(sliceSize, r) 93 | 94 | b.Run("DropInt64", func(b *testing.B) { 95 | for n := 0; n < b.N; n++ { 96 | DropInt64(testData, 1) 97 | } 98 | }) 99 | 100 | b.Run("Drop", func(b *testing.B) { 101 | for n := 0; n < b.N; n++ { 102 | Drop(testData, 1) 103 | } 104 | }) 105 | } 106 | 107 | func BenchmarkJoin(b *testing.B) { 108 | r := rand.New(rand.NewSource(seed)) 109 | fullArr := sliceGenerator(sliceSize, r) 110 | leftArr := fullArr[:sliceSize/3*2] 111 | rightArr := fullArr[sliceSize/3*1:] 112 | 113 | b.Run("InnerJoinInt64", func(b *testing.B) { 114 | for n := 0; n < b.N; n++ { 115 | JoinInt64(leftArr, rightArr, InnerJoinInt64) 116 | } 117 | }) 118 | 119 | b.Run("InnerJoin", func(b *testing.B) { 120 | for n := 0; n < b.N; n++ { 121 | Join(leftArr, rightArr, InnerJoin) 122 | } 123 | }) 124 | } 125 | -------------------------------------------------------------------------------- /builder.go: -------------------------------------------------------------------------------- 1 | package funk 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | // Builder contains all tools which can be chained. 9 | type Builder interface { 10 | Chunk(size int) Builder 11 | Compact() Builder 12 | Drop(n int) Builder 13 | Filter(predicate interface{}) Builder 14 | Flatten() Builder 15 | FlattenDeep() Builder 16 | Initial() Builder 17 | Intersect(y interface{}) Builder 18 | Join(rarr interface{}, fnc JoinFnc) Builder 19 | Map(mapFunc interface{}) Builder 20 | FlatMap(mapFunc interface{}) Builder 21 | Reverse() Builder 22 | Shuffle() Builder 23 | Tail() Builder 24 | Uniq() Builder 25 | Without(values ...interface{}) Builder 26 | 27 | All() bool 28 | Any() bool 29 | Contains(elem interface{}) bool 30 | Every(elements ...interface{}) bool 31 | Find(predicate interface{}) interface{} 32 | ForEach(predicate interface{}) 33 | ForEachRight(predicate interface{}) 34 | Head() interface{} 35 | Keys() interface{} 36 | IndexOf(elem interface{}) int 37 | IsEmpty() bool 38 | Last() interface{} 39 | LastIndexOf(elem interface{}) int 40 | NotEmpty() bool 41 | Product() float64 42 | Reduce(reduceFunc, acc interface{}) interface{} 43 | Sum() float64 44 | Type() reflect.Type 45 | Value() interface{} 46 | Values() interface{} 47 | } 48 | 49 | // Chain creates a simple new go-funk.Builder from a collection. Each method 50 | // call generate a new builder containing the previous result. 51 | func Chain(v interface{}) Builder { 52 | isNotNil(v, "Chain") 53 | 54 | valueType := reflect.TypeOf(v) 55 | if isValidBuilderEntry(valueType) || 56 | (valueType.Kind() == reflect.Ptr && isValidBuilderEntry(valueType.Elem())) { 57 | return &chainBuilder{v} 58 | } 59 | 60 | panic(fmt.Sprintf("Type %s is not supported by Chain", valueType.String())) 61 | } 62 | 63 | // LazyChain creates a lazy go-funk.Builder from a collection. Each method call 64 | // generate a new builder containing a method generating the previous value. 65 | // With that, all data are only generated when we call a tailling method like All or Find. 66 | func LazyChain(v interface{}) Builder { 67 | isNotNil(v, "LazyChain") 68 | 69 | valueType := reflect.TypeOf(v) 70 | if isValidBuilderEntry(valueType) || 71 | (valueType.Kind() == reflect.Ptr && isValidBuilderEntry(valueType.Elem())) { 72 | return &lazyBuilder{func() interface{} { return v }} 73 | } 74 | 75 | panic(fmt.Sprintf("Type %s is not supported by LazyChain", valueType.String())) 76 | } 77 | 78 | // LazyChainWith creates a lazy go-funk.Builder from a generator. Like LazyChain, each 79 | // method call generate a new builder containing a method generating the previous value. 80 | // But, instead of using a collection, it takes a generator which can generate values. 81 | // With LazyChainWith, to can create a generic pipeline of collection transformation and, 82 | // throw the generator, sending different collection. 83 | func LazyChainWith(generator func() interface{}) Builder { 84 | isNotNil(generator, "LazyChainWith") 85 | return &lazyBuilder{func() interface{} { 86 | isNotNil(generator, "LazyChainWith") 87 | 88 | v := generator() 89 | valueType := reflect.TypeOf(v) 90 | if isValidBuilderEntry(valueType) || 91 | (valueType.Kind() == reflect.Ptr && isValidBuilderEntry(valueType.Elem())) { 92 | return v 93 | } 94 | 95 | panic(fmt.Sprintf("Type %s is not supported by LazyChainWith generator", valueType.String())) 96 | }} 97 | } 98 | 99 | func isNotNil(v interface{}, from string) { 100 | if v == nil { 101 | panic(fmt.Sprintf("nil value is not supported by %s", from)) 102 | } 103 | } 104 | 105 | func isValidBuilderEntry(valueType reflect.Type) bool { 106 | return valueType.Kind() == reflect.Slice || valueType.Kind() == reflect.Array || 107 | valueType.Kind() == reflect.Map || 108 | valueType.Kind() == reflect.String 109 | } 110 | -------------------------------------------------------------------------------- /builder_test.go: -------------------------------------------------------------------------------- 1 | package funk 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestChain(t *testing.T) { 11 | testCases := []struct { 12 | In interface{} 13 | Panic string 14 | }{ 15 | // Check with array types 16 | {In: []int{0, 1, 2}}, 17 | {In: []string{"aaa", "bbb", "ccc"}}, 18 | {In: []interface{}{0, false, "___"}}, 19 | 20 | // Check with map types 21 | {In: map[int]string{0: "aaa", 1: "bbb", 2: "ccc"}}, 22 | {In: map[string]string{"0": "aaa", "1": "bbb", "2": "ccc"}}, 23 | {In: map[int]interface{}{0: 0, 1: false, 2: "___"}}, 24 | 25 | // Check with invalid types 26 | {false, "Type bool is not supported by Chain"}, 27 | {0, "Type int is not supported by Chain"}, 28 | } 29 | 30 | for idx, tc := range testCases { 31 | t.Run(fmt.Sprintf("test case #%d", idx+1), func(t *testing.T) { 32 | is := assert.New(t) 33 | 34 | if tc.Panic != "" { 35 | is.PanicsWithValue(tc.Panic, func() { 36 | Chain(tc.In) 37 | }) 38 | return 39 | } 40 | 41 | chain := Chain(tc.In) 42 | collection := chain.(*chainBuilder).collection 43 | 44 | is.Equal(collection, tc.In) 45 | }) 46 | } 47 | } 48 | 49 | func TestLazyChain(t *testing.T) { 50 | testCases := []struct { 51 | In interface{} 52 | Panic string 53 | }{ 54 | // Check with array types 55 | {In: []int{0, 1, 2}}, 56 | {In: []string{"aaa", "bbb", "ccc"}}, 57 | {In: []interface{}{0, false, "___"}}, 58 | 59 | // Check with map types 60 | {In: map[int]string{0: "aaa", 1: "bbb", 2: "ccc"}}, 61 | {In: map[string]string{"0": "aaa", "1": "bbb", "2": "ccc"}}, 62 | {In: map[int]interface{}{0: 0, 1: false, 2: "___"}}, 63 | 64 | // Check with invalid types 65 | {false, "Type bool is not supported by LazyChain"}, 66 | {0, "Type int is not supported by LazyChain"}, 67 | } 68 | 69 | for idx, tc := range testCases { 70 | t.Run(fmt.Sprintf("test case #%d", idx+1), func(t *testing.T) { 71 | is := assert.New(t) 72 | 73 | if tc.Panic != "" { 74 | is.PanicsWithValue(tc.Panic, func() { 75 | LazyChain(tc.In) 76 | }) 77 | return 78 | } 79 | 80 | chain := LazyChain(tc.In) 81 | collection := chain.(*lazyBuilder).exec() 82 | 83 | is.Equal(collection, tc.In) 84 | }) 85 | } 86 | } 87 | 88 | func TestLazyChainWith(t *testing.T) { 89 | testCases := []struct { 90 | In func() interface{} 91 | Panic string 92 | }{ 93 | // Check with array types 94 | {In: func() interface{} { return []int{0, 1, 2} }}, 95 | {In: func() interface{} { return []string{"aaa", "bbb", "ccc"} }}, 96 | {In: func() interface{} { return []interface{}{0, false, "___"} }}, 97 | 98 | // Check with map types 99 | {In: func() interface{} { return map[int]string{0: "aaa", 1: "bbb", 2: "ccc"} }}, 100 | {In: func() interface{} { return map[string]string{"0": "aaa", "1": "bbb", "2": "ccc"} }}, 101 | {In: func() interface{} { return map[int]interface{}{0: 0, 1: false, 2: "___"} }}, 102 | 103 | // Check with invalid types 104 | { 105 | In: func() interface{} { return false }, 106 | Panic: "Type bool is not supported by LazyChainWith generator", 107 | }, 108 | { 109 | In: func() interface{} { return 0 }, 110 | Panic: "Type int is not supported by LazyChainWith generator", 111 | }, 112 | } 113 | 114 | for idx, tc := range testCases { 115 | t.Run(fmt.Sprintf("test case #%d", idx+1), func(t *testing.T) { 116 | is := assert.New(t) 117 | 118 | if tc.Panic != "" { 119 | is.PanicsWithValue(tc.Panic, func() { 120 | LazyChainWith(tc.In).(*lazyBuilder).exec() 121 | }) 122 | return 123 | } 124 | 125 | chain := LazyChainWith(tc.In) 126 | collection := chain.(*lazyBuilder).exec() 127 | 128 | is.Equal(collection, tc.In()) 129 | }) 130 | } 131 | } 132 | 133 | func ExampleChain() { 134 | v := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} 135 | chain := Chain(v) 136 | lazy := LazyChain(v) 137 | 138 | // Without builder 139 | a := Filter(v, func(x int) bool { return x%2 == 0 }) 140 | b := Map(a, func(x int) int { return x * 2 }) 141 | c := Reverse(a) 142 | fmt.Printf("funk.Contains(b, 2): %v\n", Contains(b, 2)) // false 143 | fmt.Printf("funk.Contains(b, 4): %v\n", Contains(b, 4)) // true 144 | fmt.Printf("funk.Sum(b): %v\n", Sum(b)) // 40 145 | fmt.Printf("funk.Head(b): %v\n", Head(b)) // 4 146 | fmt.Printf("funk.Head(c): %v\n\n", Head(c)) // 8 147 | 148 | // With simple chain builder 149 | ca := chain.Filter(func(x int) bool { return x%2 == 0 }) 150 | cb := ca.Map(func(x int) int { return x * 2 }) 151 | cc := ca.Reverse() 152 | fmt.Printf("chainB.Contains(2): %v\n", cb.Contains(2)) // false 153 | fmt.Printf("chainB.Contains(4): %v\n", cb.Contains(4)) // true 154 | fmt.Printf("chainB.Sum(): %v\n", cb.Sum()) // 40 155 | fmt.Printf("chainB.Head(): %v\n", cb.Head()) // 4 156 | fmt.Printf("chainC.Head(): %v\n\n", cc.Head()) // 8 157 | 158 | // With lazy chain builder 159 | la := lazy.Filter(func(x int) bool { return x%2 == 0 }) 160 | lb := la.Map(func(x int) int { return x * 2 }) 161 | lc := la.Reverse() 162 | fmt.Printf("lazyChainB.Contains(2): %v\n", lb.Contains(2)) // false 163 | fmt.Printf("lazyChainB.Contains(4): %v\n", lb.Contains(4)) // true 164 | fmt.Printf("lazyChainB.Sum(): %v\n", lb.Sum()) // 40 165 | fmt.Printf("lazyChainB.Head(): %v\n", lb.Head()) // 4 166 | fmt.Printf("lazyChainC.Head(): %v\n", lc.Head()) // 8 167 | } 168 | 169 | type updatingStruct struct { 170 | x []int 171 | } 172 | 173 | func (us *updatingStruct) Values() interface{} { 174 | return us.x 175 | } 176 | 177 | func ExampleLazyChain() { 178 | us := updatingStruct{} 179 | chain := Chain(us.x). 180 | Map(func(x int) float64 { return float64(x) * 2.5 }) 181 | lazy := LazyChain(us.x). 182 | Map(func(x int) float64 { return float64(x) * 2.5 }) 183 | lazyWith := LazyChainWith(us.Values). 184 | Map(func(x int) float64 { return float64(x) * 2.5 }) 185 | 186 | fmt.Printf("chain.Sum(): %v\n", chain.Sum()) // 0 187 | fmt.Printf("lazy.Sum(): %v\n", lazy.Sum()) // 0 188 | fmt.Printf("lazyWith.Sum(): %v\n\n", lazyWith.Sum()) // 0 189 | 190 | us.x = append(us.x, 2) 191 | fmt.Printf("chain.Sum(): %v\n", chain.Sum()) // 0 192 | fmt.Printf("lazy.Sum(): %v\n", lazy.Sum()) // 0 193 | fmt.Printf("lazyWith.Sum(): %v\n\n", lazyWith.Sum()) // 5 194 | 195 | us.x = append(us.x, 10) 196 | fmt.Printf("chain.Sum(): %v\n", chain.Sum()) // 0 197 | fmt.Printf("lazy.Sum(): %v\n", lazy.Sum()) // 0 198 | fmt.Printf("lazyWith.Sum(): %v\n\n", lazyWith.Sum()) // 30 199 | } 200 | -------------------------------------------------------------------------------- /chain_builder.go: -------------------------------------------------------------------------------- 1 | package funk 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | type chainBuilder struct { 9 | collection interface{} 10 | } 11 | 12 | func (b *chainBuilder) Chunk(size int) Builder { 13 | return &chainBuilder{Chunk(b.collection, size)} 14 | } 15 | func (b *chainBuilder) Compact() Builder { 16 | return &chainBuilder{Compact(b.collection)} 17 | } 18 | func (b *chainBuilder) Drop(n int) Builder { 19 | return &chainBuilder{Drop(b.collection, n)} 20 | } 21 | func (b *chainBuilder) Filter(predicate interface{}) Builder { 22 | return &chainBuilder{Filter(b.collection, predicate)} 23 | } 24 | func (b *chainBuilder) Flatten() Builder { 25 | return &chainBuilder{Flatten(b.collection)} 26 | } 27 | func (b *chainBuilder) FlattenDeep() Builder { 28 | return &chainBuilder{FlattenDeep(b.collection)} 29 | } 30 | func (b *chainBuilder) Initial() Builder { 31 | return &chainBuilder{Initial(b.collection)} 32 | } 33 | func (b *chainBuilder) Intersect(y interface{}) Builder { 34 | return &chainBuilder{Intersect(b.collection, y)} 35 | } 36 | func (b *chainBuilder) Join(rarr interface{}, fnc JoinFnc) Builder { 37 | return &chainBuilder{Join(b.collection, rarr, fnc)} 38 | } 39 | func (b *chainBuilder) Map(mapFunc interface{}) Builder { 40 | return &chainBuilder{Map(b.collection, mapFunc)} 41 | } 42 | func (b *chainBuilder) FlatMap(mapFunc interface{}) Builder { 43 | return &chainBuilder{FlatMap(b.collection, mapFunc)} 44 | } 45 | func (b *chainBuilder) Reverse() Builder { 46 | return &chainBuilder{Reverse(b.collection)} 47 | } 48 | func (b *chainBuilder) Shuffle() Builder { 49 | return &chainBuilder{Shuffle(b.collection)} 50 | } 51 | func (b *chainBuilder) Tail() Builder { 52 | return &chainBuilder{Tail(b.collection)} 53 | } 54 | func (b *chainBuilder) Uniq() Builder { 55 | return &chainBuilder{Uniq(b.collection)} 56 | } 57 | func (b *chainBuilder) Without(values ...interface{}) Builder { 58 | return &chainBuilder{Without(b.collection, values...)} 59 | } 60 | 61 | func (b *chainBuilder) All() bool { 62 | v := reflect.ValueOf(b.collection) 63 | t := v.Type() 64 | 65 | if t.Kind() != reflect.Array && t.Kind() != reflect.Slice { 66 | panic(fmt.Sprintf("Type %s is not supported by Chain.All", t.String())) 67 | } 68 | 69 | c := make([]interface{}, v.Len()) 70 | for i := 0; i < v.Len(); i++ { 71 | c[i] = v.Index(i).Interface() 72 | } 73 | return All(c...) 74 | } 75 | func (b *chainBuilder) Any() bool { 76 | v := reflect.ValueOf(b.collection) 77 | t := v.Type() 78 | 79 | if t.Kind() != reflect.Array && t.Kind() != reflect.Slice { 80 | panic(fmt.Sprintf("Type %s is not supported by Chain.Any", t.String())) 81 | } 82 | 83 | c := make([]interface{}, v.Len()) 84 | for i := 0; i < v.Len(); i++ { 85 | c[i] = v.Index(i).Interface() 86 | } 87 | return Any(c...) 88 | } 89 | func (b *chainBuilder) Contains(elem interface{}) bool { 90 | return Contains(b.collection, elem) 91 | } 92 | func (b *chainBuilder) Every(elements ...interface{}) bool { 93 | return Every(b.collection, elements...) 94 | } 95 | func (b *chainBuilder) Find(predicate interface{}) interface{} { 96 | return Find(b.collection, predicate) 97 | } 98 | func (b *chainBuilder) ForEach(predicate interface{}) { 99 | ForEach(b.collection, predicate) 100 | } 101 | func (b *chainBuilder) ForEachRight(predicate interface{}) { 102 | ForEachRight(b.collection, predicate) 103 | } 104 | func (b *chainBuilder) Head() interface{} { 105 | return Head(b.collection) 106 | } 107 | func (b *chainBuilder) Keys() interface{} { 108 | return Keys(b.collection) 109 | } 110 | func (b *chainBuilder) IndexOf(elem interface{}) int { 111 | return IndexOf(b.collection, elem) 112 | } 113 | func (b *chainBuilder) IsEmpty() bool { 114 | return IsEmpty(b.collection) 115 | } 116 | func (b *chainBuilder) Last() interface{} { 117 | return Last(b.collection) 118 | } 119 | func (b *chainBuilder) LastIndexOf(elem interface{}) int { 120 | return LastIndexOf(b.collection, elem) 121 | } 122 | func (b *chainBuilder) NotEmpty() bool { 123 | return NotEmpty(b.collection) 124 | } 125 | func (b *chainBuilder) Product() float64 { 126 | return Product(b.collection) 127 | } 128 | func (b *chainBuilder) Reduce(reduceFunc, acc interface{}) interface{} { 129 | return Reduce(b.collection, reduceFunc, acc) 130 | } 131 | func (b *chainBuilder) Sum() float64 { 132 | return Sum(b.collection) 133 | } 134 | func (b *chainBuilder) Type() reflect.Type { 135 | return reflect.TypeOf(b.collection) 136 | } 137 | func (b *chainBuilder) Value() interface{} { 138 | return b.collection 139 | } 140 | func (b *chainBuilder) Values() interface{} { 141 | return Values(b.collection) 142 | } 143 | -------------------------------------------------------------------------------- /compact.go: -------------------------------------------------------------------------------- 1 | package funk 2 | 3 | import ( 4 | "reflect" 5 | ) 6 | 7 | // Compact creates a slice with all empty/zero values removed. 8 | func Compact(value interface{}) interface{} { 9 | arr := redirectValue(reflect.ValueOf(value)) 10 | 11 | if arr.Kind() != reflect.Slice && arr.Kind() != reflect.Array { 12 | panic("First parameter must be array or slice") 13 | } 14 | 15 | sliceElemType := sliceElem(arr.Type()) 16 | resultSliceType := reflect.SliceOf(sliceElemType) 17 | result := reflect.MakeSlice(resultSliceType, 0, 0) 18 | 19 | for i := 0; i < arr.Len(); i++ { 20 | elemVal := arr.Index(i) 21 | 22 | if elemVal.Kind() == reflect.Interface { 23 | elemVal = elemVal.Elem() 24 | } 25 | 26 | redirectedElemVal := redirectValue(elemVal) 27 | 28 | switch redirectedElemVal.Kind() { 29 | case reflect.Invalid: 30 | continue 31 | case reflect.Func: 32 | if redirectedElemVal.IsNil() { 33 | continue 34 | } 35 | case reflect.Map, reflect.Slice, reflect.Chan: 36 | if redirectedElemVal.Len() == 0 { 37 | continue 38 | } 39 | default: 40 | defaultValue := reflect.Zero(redirectedElemVal.Type()).Interface() 41 | if redirectedElemVal.Interface() == defaultValue { 42 | continue 43 | } 44 | } 45 | 46 | result = reflect.Append(result, elemVal) 47 | } 48 | 49 | return result.Interface() 50 | } 51 | -------------------------------------------------------------------------------- /compact_test.go: -------------------------------------------------------------------------------- 1 | package funk 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestCompact(t *testing.T) { 11 | var emptyFunc func() bool 12 | emptyFuncPtr := &emptyFunc 13 | 14 | nonEmptyFunc := func() bool { return true } 15 | nonEmptyFuncPtr := &nonEmptyFunc 16 | 17 | nonEmptyMap := map[int]int{1: 2} 18 | nonEmptyMapPtr := &nonEmptyMap 19 | 20 | var emptyMap map[int]int 21 | emptyMapPtr := &emptyMap 22 | 23 | var emptyChan chan bool 24 | nonEmptyChan := make(chan bool, 1) 25 | nonEmptyChan <- true 26 | 27 | emptyChanPtr := &emptyChan 28 | nonEmptyChanPtr := &nonEmptyChan 29 | 30 | var emptyString string 31 | emptyStringPtr := &emptyString 32 | 33 | nonEmptyString := "42" 34 | nonEmptyStringPtr := &nonEmptyString 35 | 36 | testCases := []struct { 37 | Arr interface{} 38 | Result interface{} 39 | }{ 40 | // Check with nils 41 | { 42 | []interface{}{42, nil, (*int)(nil)}, 43 | []interface{}{42}, 44 | }, 45 | 46 | // Check with functions 47 | { 48 | []interface{}{42, emptyFuncPtr, emptyFunc, nonEmptyFuncPtr}, 49 | []interface{}{42, nonEmptyFuncPtr}, 50 | }, 51 | 52 | // Check with slices, maps, arrays and channels 53 | { 54 | []interface{}{ 55 | 42, [2]int{}, map[int]int{}, []string{}, nonEmptyMapPtr, emptyMap, 56 | emptyMapPtr, nonEmptyMap, nonEmptyChan, emptyChan, emptyChanPtr, nonEmptyChanPtr, 57 | }, 58 | []interface{}{42, nonEmptyMapPtr, nonEmptyMap, nonEmptyChan, nonEmptyChanPtr}, 59 | }, 60 | 61 | // Check with strings, numbers and booleans 62 | { 63 | []interface{}{true, 0, float64(0), "", "42", emptyStringPtr, nonEmptyStringPtr, false}, 64 | []interface{}{true, "42", nonEmptyStringPtr}, 65 | }, 66 | } 67 | 68 | for idx, tc := range testCases { 69 | t.Run(fmt.Sprintf("test case #%d", idx+1), func(t *testing.T) { 70 | is := assert.New(t) 71 | result := Compact(tc.Arr) 72 | 73 | if !is.Equal(result, tc.Result) { 74 | t.Errorf("%#v doesn't equal to %#v", result, tc.Result) 75 | } 76 | }) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /example_presence_test.go: -------------------------------------------------------------------------------- 1 | package funk 2 | 3 | import "fmt" 4 | 5 | func ExampleSome() { 6 | a := []string{"foo", "bar", "baz"} 7 | fmt.Println(Some(a, "foo", "qux")) 8 | 9 | b := "Mark Shaun" 10 | fmt.Println(Some(b, "Marc", "Sean")) 11 | 12 | // Output: true 13 | // false 14 | } 15 | -------------------------------------------------------------------------------- /fill.go: -------------------------------------------------------------------------------- 1 | package funk 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "reflect" 7 | ) 8 | 9 | // Fill fills elements of array with value 10 | func Fill(in interface{}, fillValue interface{}) (interface{}, error) { 11 | inValue := reflect.ValueOf(in) 12 | inKind := inValue.Type().Kind() 13 | if inKind != reflect.Slice && inKind != reflect.Array { 14 | return nil, errors.New("can only fill slices and arrays") 15 | } 16 | 17 | inType := reflect.TypeOf(in).Elem() 18 | value := reflect.ValueOf(fillValue) 19 | if inType != value.Type() { 20 | return nil, fmt.Errorf( 21 | "cannot fill '%s' with '%s'", reflect.TypeOf(in), value.Type(), 22 | ) 23 | } 24 | 25 | length := inValue.Len() 26 | newSlice := reflect.SliceOf(reflect.TypeOf(fillValue)) 27 | in = reflect.MakeSlice(newSlice, length, length).Interface() 28 | inValue = reflect.ValueOf(in) 29 | 30 | for i := 0; i < length; i++ { 31 | inValue.Index(i).Set(value) 32 | } 33 | return in, nil 34 | } 35 | -------------------------------------------------------------------------------- /fill_test.go: -------------------------------------------------------------------------------- 1 | package funk 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestFillMismatchedTypes(t *testing.T) { 10 | _, err := Fill([]string{"a", "b"}, 1) 11 | assert.EqualError(t, err, "cannot fill '[]string' with 'int'") 12 | } 13 | 14 | func TestFillUnfillableTypes(t *testing.T) { 15 | var stringVariable string 16 | var uint32Variable uint32 17 | var boolVariable bool 18 | 19 | types := [](interface{}){ 20 | stringVariable, 21 | uint32Variable, 22 | boolVariable, 23 | } 24 | 25 | for _, unfillable := range types { 26 | _, err := Fill(unfillable, 1) 27 | assert.EqualError(t, err, "can only fill slices and arrays") 28 | } 29 | } 30 | 31 | func TestFillSlice(t *testing.T) { 32 | input := []int{1, 2, 3} 33 | result, err := Fill(input, 1) 34 | assert.NoError(t, err) 35 | assert.Equal(t, []int{1, 1, 1}, result) 36 | 37 | // Assert that input does not change 38 | assert.Equal(t, []int{1, 2, 3}, input) 39 | } 40 | 41 | func TestFillArray(t *testing.T) { 42 | input := [...]int{1, 2, 3} 43 | result, err := Fill(input, 2) 44 | assert.NoError(t, err) 45 | assert.Equal(t, []int{2, 2, 2}, result) 46 | 47 | // Assert that input does not change 48 | assert.Equal(t, [...]int{1, 2, 3}, input) 49 | } 50 | -------------------------------------------------------------------------------- /funk_test.go: -------------------------------------------------------------------------------- 1 | package funk 2 | 3 | import "database/sql" 4 | 5 | type Model interface { 6 | TableName() string 7 | } 8 | 9 | // Bar is 10 | type Bar struct { 11 | Name string `tag_name:"BarName"` 12 | Bar *Bar 13 | Bars []*Bar 14 | } 15 | 16 | func (b Bar) TableName() string { 17 | return "bar" 18 | } 19 | 20 | // Foo is 21 | type Foo struct { 22 | ID int 23 | FirstName string `tag_name:"tag 1"` 24 | LastName string `tag_name:"tag 2"` 25 | Age int `tag_name:"tag 3"` 26 | Bar *Bar `tag_name:"tag 4"` 27 | Bars []*Bar 28 | EmptyValue sql.NullInt64 29 | 30 | BarInterface interface{} 31 | BarPointer interface{} 32 | GeneralInterface interface{} 33 | 34 | ZeroBoolValue bool 35 | ZeroIntValue int 36 | ZeroIntPtrValue *int 37 | } 38 | 39 | func (f Foo) TableName() string { 40 | return "foo" 41 | } 42 | 43 | var bar = &Bar{ 44 | Name: "Test", 45 | Bars: []*Bar{ 46 | { 47 | Name: "Level1-1", 48 | Bar: &Bar{ 49 | Name: "Level2-1", 50 | }, 51 | }, 52 | { 53 | Name: "Level1-2", 54 | Bar: &Bar{ 55 | Name: "Level2-2", 56 | }, 57 | }, 58 | }, 59 | } 60 | 61 | var foo = &Foo{ 62 | ID: 1, 63 | FirstName: "Dark", 64 | LastName: "Vador", 65 | Age: 30, 66 | Bar: bar, 67 | EmptyValue: sql.NullInt64{ 68 | Valid: true, 69 | Int64: 10, 70 | }, 71 | Bars: []*Bar{ 72 | bar, 73 | bar, 74 | }, 75 | BarInterface: bar, 76 | BarPointer: &bar, 77 | ZeroBoolValue: false, 78 | ZeroIntValue: 0, 79 | ZeroIntPtrValue: nil, 80 | } 81 | 82 | var foo2 = &Foo{ 83 | ID: 1, 84 | FirstName: "Dark", 85 | LastName: "Vador", 86 | Age: 30, 87 | } 88 | 89 | var m1 = map[string]interface{}{ 90 | "id": 1, 91 | "firstname": "dark", 92 | "lastname": "vador", 93 | "age": 30, 94 | "bar": map[string]interface{}{ 95 | "name": "test", 96 | "bars": []map[string]interface{}{ 97 | { 98 | "name": "level1-1", 99 | "bar": map[string]interface{}{ 100 | "name": "level2-1", 101 | }, 102 | }, 103 | { 104 | "name": "level1-2", 105 | "bar": map[string]interface{}{ 106 | "name": "level2-2", 107 | }, 108 | }, 109 | }, 110 | }, 111 | } 112 | 113 | var m2 = map[string]interface{}{ 114 | "id": 1, 115 | "firstname": "dark", 116 | "lastname": "vador", 117 | "age": 30, 118 | } 119 | 120 | type FooUnexported struct { 121 | unexported bool 122 | } 123 | 124 | var fooUnexported = &FooUnexported{ 125 | unexported: true, 126 | } 127 | 128 | type EmbeddedStruct struct { 129 | EmbeddedField *string 130 | } 131 | 132 | type RootStructPointer struct { 133 | *EmbeddedStruct 134 | 135 | RootField *string 136 | } 137 | 138 | type RootStructNotPointer struct { 139 | EmbeddedStruct 140 | 141 | RootField *string 142 | } 143 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/thoas/go-funk 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/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 6 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= 7 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 8 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 9 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 10 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= 11 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 12 | -------------------------------------------------------------------------------- /helpers.go: -------------------------------------------------------------------------------- 1 | package funk 2 | 3 | import ( 4 | "bytes" 5 | "math/rand" 6 | "reflect" 7 | ) 8 | 9 | var numericZeros = []interface{}{ 10 | int(0), 11 | int8(0), 12 | int16(0), 13 | int32(0), 14 | int64(0), 15 | uint(0), 16 | uint8(0), 17 | uint16(0), 18 | uint32(0), 19 | uint64(0), 20 | float32(0), 21 | float64(0), 22 | } 23 | 24 | // ToFloat64 converts any numeric value to float64. 25 | func ToFloat64(x interface{}) (float64, bool) { 26 | var xf float64 27 | xok := true 28 | 29 | switch xn := x.(type) { 30 | case uint8: 31 | xf = float64(xn) 32 | case uint16: 33 | xf = float64(xn) 34 | case uint32: 35 | xf = float64(xn) 36 | case uint64: 37 | xf = float64(xn) 38 | case int: 39 | xf = float64(xn) 40 | case int8: 41 | xf = float64(xn) 42 | case int16: 43 | xf = float64(xn) 44 | case int32: 45 | xf = float64(xn) 46 | case int64: 47 | xf = float64(xn) 48 | case float32: 49 | xf = float64(xn) 50 | case float64: 51 | xf = float64(xn) 52 | default: 53 | xok = false 54 | } 55 | 56 | return xf, xok 57 | } 58 | 59 | // PtrOf makes a copy of the given interface and returns a pointer. 60 | func PtrOf(itf interface{}) interface{} { 61 | t := reflect.TypeOf(itf) 62 | 63 | cp := reflect.New(t) 64 | cp.Elem().Set(reflect.ValueOf(itf)) 65 | 66 | // Avoid double pointers if itf is a pointer 67 | if t.Kind() == reflect.Ptr { 68 | return cp.Elem().Interface() 69 | } 70 | 71 | return cp.Interface() 72 | } 73 | 74 | // IsFunction returns if the argument is a function. 75 | func IsFunction(in interface{}, num ...int) bool { 76 | funcType := reflect.TypeOf(in) 77 | 78 | result := funcType != nil && funcType.Kind() == reflect.Func 79 | 80 | if len(num) >= 1 { 81 | result = result && funcType.NumIn() == num[0] 82 | } 83 | 84 | if len(num) == 2 { 85 | result = result && funcType.NumOut() == num[1] 86 | } 87 | 88 | return result 89 | } 90 | 91 | // IsPredicate returns if the argument is a predicate function. 92 | func IsPredicate(in interface{}, inTypes ...reflect.Type) bool { 93 | if len(inTypes) == 0 { 94 | inTypes = append(inTypes, nil) 95 | } 96 | 97 | funcType := reflect.TypeOf(in) 98 | 99 | result := funcType != nil && funcType.Kind() == reflect.Func 100 | 101 | result = result && funcType.NumOut() == 1 && funcType.Out(0).Kind() == reflect.Bool 102 | result = result && funcType.NumIn() == len(inTypes) 103 | 104 | for i := 0; result && i < len(inTypes); i++ { 105 | inType := inTypes[i] 106 | result = inType == nil || inType.ConvertibleTo(funcType.In(i)) 107 | } 108 | 109 | return result 110 | } 111 | 112 | // IsEqual returns if the two objects are equal 113 | func IsEqual(expected interface{}, actual interface{}) bool { 114 | if expected == nil || actual == nil { 115 | return expected == actual 116 | } 117 | 118 | if exp, ok := expected.([]byte); ok { 119 | act, ok := actual.([]byte) 120 | if !ok { 121 | return false 122 | } 123 | 124 | if exp == nil || act == nil { 125 | return true 126 | } 127 | 128 | return bytes.Equal(exp, act) 129 | } 130 | 131 | return reflect.DeepEqual(expected, actual) 132 | 133 | } 134 | 135 | // IsType returns if the two objects are in the same type 136 | func IsType(expected interface{}, actual interface{}) bool { 137 | return IsEqual(reflect.TypeOf(expected), reflect.TypeOf(actual)) 138 | } 139 | 140 | // Equal returns if the two objects are equal 141 | func Equal(expected interface{}, actual interface{}) bool { 142 | return IsEqual(expected, actual) 143 | } 144 | 145 | // NotEqual returns if the two objects are not equal 146 | func NotEqual(expected interface{}, actual interface{}) bool { 147 | return !IsEqual(expected, actual) 148 | } 149 | 150 | // IsIteratee returns if the argument is an iteratee. 151 | func IsIteratee(in interface{}) bool { 152 | if in == nil { 153 | return false 154 | } 155 | arrType := reflect.TypeOf(in) 156 | 157 | kind := arrType.Kind() 158 | 159 | return kind == reflect.Array || kind == reflect.Slice || kind == reflect.Map 160 | } 161 | 162 | // IsCollection returns if the argument is a collection. 163 | func IsCollection(in interface{}) bool { 164 | arrType := reflect.TypeOf(in) 165 | 166 | kind := arrType.Kind() 167 | 168 | return kind == reflect.Array || kind == reflect.Slice 169 | } 170 | 171 | // SliceOf returns a slice which contains the element. 172 | func SliceOf(in interface{}) interface{} { 173 | value := reflect.ValueOf(in) 174 | 175 | sliceType := reflect.SliceOf(reflect.TypeOf(in)) 176 | slice := reflect.New(sliceType) 177 | sliceValue := reflect.MakeSlice(sliceType, 0, 0) 178 | sliceValue = reflect.Append(sliceValue, value) 179 | slice.Elem().Set(sliceValue) 180 | 181 | return slice.Elem().Interface() 182 | } 183 | 184 | // Any returns true if any element of the iterable is not empty. If the iterable is empty, return False. 185 | func Any(objs ...interface{}) bool { 186 | if len(objs) == 0 { 187 | return false 188 | } 189 | 190 | for _, obj := range objs { 191 | if !IsEmpty(obj) { 192 | return true 193 | } 194 | } 195 | 196 | return false 197 | } 198 | 199 | // All returns true if all elements of the iterable are not empty (or if the iterable is empty) 200 | func All(objs ...interface{}) bool { 201 | if len(objs) == 0 { 202 | return true 203 | } 204 | 205 | for _, obj := range objs { 206 | if IsEmpty(obj) { 207 | return false 208 | } 209 | } 210 | 211 | return true 212 | } 213 | 214 | // IsEmpty returns if the object is considered as empty or not. 215 | func IsEmpty(obj interface{}) bool { 216 | if obj == nil || obj == "" || obj == false { 217 | return true 218 | } 219 | 220 | for _, v := range numericZeros { 221 | if obj == v { 222 | return true 223 | } 224 | } 225 | 226 | objValue := reflect.ValueOf(obj) 227 | 228 | switch objValue.Kind() { 229 | case reflect.Map: 230 | fallthrough 231 | case reflect.Slice, reflect.Chan: 232 | return objValue.Len() == 0 233 | case reflect.Struct: 234 | return reflect.DeepEqual(obj, ZeroOf(obj)) 235 | case reflect.Ptr: 236 | if objValue.IsNil() { 237 | return true 238 | } 239 | 240 | obj = redirectValue(objValue).Interface() 241 | 242 | return reflect.DeepEqual(obj, ZeroOf(obj)) 243 | } 244 | 245 | return false 246 | } 247 | 248 | // IsZero returns if the object is considered as zero value 249 | func IsZero(obj interface{}) bool { 250 | if obj == nil || obj == "" || obj == false { 251 | return true 252 | } 253 | 254 | for _, v := range numericZeros { 255 | if obj == v { 256 | return true 257 | } 258 | } 259 | 260 | return reflect.DeepEqual(obj, ZeroOf(obj)) 261 | } 262 | 263 | // NotEmpty returns if the object is considered as non-empty or not. 264 | func NotEmpty(obj interface{}) bool { 265 | return !IsEmpty(obj) 266 | } 267 | 268 | // ZeroOf returns a zero value of an element. 269 | func ZeroOf(in interface{}) interface{} { 270 | if in == nil { 271 | return nil 272 | } 273 | 274 | return reflect.Zero(reflect.TypeOf(in)).Interface() 275 | } 276 | 277 | // RandomInt generates a random int, based on a min and max values 278 | func RandomInt(min, max int) int { 279 | return min + rand.Intn(max-min) 280 | } 281 | 282 | // Shard will shard a string name 283 | func Shard(str string, width int, depth int, restOnly bool) []string { 284 | var results []string 285 | 286 | for i := 0; i < depth; i++ { 287 | results = append(results, str[(width*i):(width*(i+1))]) 288 | } 289 | 290 | if restOnly { 291 | results = append(results, str[(width*depth):]) 292 | } else { 293 | results = append(results, str) 294 | } 295 | 296 | return results 297 | } 298 | 299 | var defaultLetters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") 300 | 301 | // RandomString returns a random string with a fixed length 302 | func RandomString(n int, allowedChars ...[]rune) string { 303 | var letters []rune 304 | 305 | if len(allowedChars) == 0 { 306 | letters = defaultLetters 307 | } else { 308 | letters = allowedChars[0] 309 | } 310 | 311 | b := make([]rune, n) 312 | for i := range b { 313 | b[i] = letters[rand.Intn(len(letters))] 314 | } 315 | 316 | return string(b) 317 | } 318 | -------------------------------------------------------------------------------- /helpers_test.go: -------------------------------------------------------------------------------- 1 | package funk 2 | 3 | import ( 4 | "errors" 5 | "os" 6 | "reflect" 7 | "testing" 8 | "time" 9 | 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | var ( 14 | i interface{} 15 | zeros = []interface{}{ 16 | false, 17 | byte(0), 18 | complex64(0), 19 | complex128(0), 20 | float32(0), 21 | float64(0), 22 | int(0), 23 | int8(0), 24 | int16(0), 25 | int32(0), 26 | int64(0), 27 | rune(0), 28 | uint(0), 29 | uint8(0), 30 | uint16(0), 31 | uint32(0), 32 | uint64(0), 33 | uintptr(0), 34 | "", 35 | [0]interface{}{}, 36 | []interface{}(nil), 37 | struct{ x int }{}, 38 | (*interface{})(nil), 39 | (func())(nil), 40 | nil, 41 | interface{}(nil), 42 | map[interface{}]interface{}(nil), 43 | (chan interface{})(nil), 44 | (<-chan interface{})(nil), 45 | (chan<- interface{})(nil), 46 | } 47 | nonZeros = []interface{}{ 48 | true, 49 | byte(1), 50 | complex64(1), 51 | complex128(1), 52 | float32(1), 53 | float64(1), 54 | int(1), 55 | int8(1), 56 | int16(1), 57 | int32(1), 58 | int64(1), 59 | rune(1), 60 | uint(1), 61 | uint8(1), 62 | uint16(1), 63 | uint32(1), 64 | uint64(1), 65 | uintptr(1), 66 | "s", 67 | [1]interface{}{1}, 68 | []interface{}{}, 69 | struct{ x int }{1}, 70 | (*interface{})(&i), 71 | (func())(func() {}), 72 | interface{}(1), 73 | map[interface{}]interface{}{}, 74 | (chan interface{})(make(chan interface{})), 75 | (<-chan interface{})(make(chan interface{})), 76 | (chan<- interface{})(make(chan interface{})), 77 | } 78 | ) 79 | 80 | func TestPtrOf(t *testing.T) { 81 | is := assert.New(t) 82 | 83 | type embedType struct { 84 | value int 85 | } 86 | 87 | type anyType struct { 88 | value int 89 | embed embedType 90 | embedPtr *embedType 91 | } 92 | 93 | any := anyType{value: 1} 94 | anyPtr := &anyType{value: 1} 95 | 96 | results := []interface{}{ 97 | PtrOf(any), 98 | PtrOf(anyPtr), 99 | } 100 | 101 | for _, r := range results { 102 | is.Equal(1, r.(*anyType).value) 103 | is.Equal(reflect.ValueOf(r).Kind(), reflect.Ptr) 104 | is.Equal(reflect.ValueOf(r).Type().Elem(), reflect.TypeOf(anyType{})) 105 | } 106 | 107 | anyWithEmbed := anyType{value: 1, embed: embedType{value: 2}} 108 | anyWithEmbedPtr := anyType{value: 1, embedPtr: &embedType{value: 2}} 109 | 110 | results = []interface{}{ 111 | PtrOf(anyWithEmbed.embed), 112 | PtrOf(anyWithEmbedPtr.embedPtr), 113 | } 114 | 115 | for _, r := range results { 116 | is.Equal(2, r.(*embedType).value) 117 | is.Equal(reflect.ValueOf(r).Kind(), reflect.Ptr) 118 | is.Equal(reflect.ValueOf(r).Type().Elem(), reflect.TypeOf(embedType{})) 119 | } 120 | } 121 | 122 | func TestSliceOf(t *testing.T) { 123 | is := assert.New(t) 124 | 125 | f := &Foo{ 126 | ID: 1, 127 | FirstName: "Dark", 128 | LastName: "Vador", 129 | Age: 30, 130 | Bar: &Bar{ 131 | Name: "Test", 132 | }, 133 | } 134 | 135 | result := SliceOf(f) 136 | 137 | resultType := reflect.TypeOf(result) 138 | 139 | is.True(resultType.Kind() == reflect.Slice) 140 | is.True(resultType.Elem().Kind() == reflect.Ptr) 141 | 142 | elemType := resultType.Elem().Elem() 143 | 144 | is.True(elemType.Kind() == reflect.Struct) 145 | 146 | value := reflect.ValueOf(result) 147 | 148 | is.Equal(value.Len(), 1) 149 | 150 | _, ok := value.Index(0).Interface().(*Foo) 151 | 152 | is.True(ok) 153 | } 154 | 155 | func TestRandomInt(t *testing.T) { 156 | is := assert.New(t) 157 | 158 | is.True(RandomInt(0, 10) <= 10) 159 | } 160 | 161 | func TestShard(t *testing.T) { 162 | is := assert.New(t) 163 | 164 | tokey := "e89d66bdfdd4dd26b682cc77e23a86eb" 165 | 166 | is.Equal(Shard(tokey, 1, 2, false), []string{"e", "8", "e89d66bdfdd4dd26b682cc77e23a86eb"}) 167 | is.Equal(Shard(tokey, 2, 2, false), []string{"e8", "9d", "e89d66bdfdd4dd26b682cc77e23a86eb"}) 168 | is.Equal(Shard(tokey, 2, 3, true), []string{"e8", "9d", "66", "bdfdd4dd26b682cc77e23a86eb"}) 169 | } 170 | 171 | func TestRandomString(t *testing.T) { 172 | is := assert.New(t) 173 | 174 | is.Len(RandomString(10), 10) 175 | 176 | result := RandomString(10, []rune("abcdefg")) 177 | 178 | is.Len(result, 10) 179 | 180 | for _, char := range result { 181 | is.True(char >= []rune("a")[0] && char <= []rune("g")[0]) 182 | } 183 | } 184 | 185 | func TestIsEmpty(t *testing.T) { 186 | is := assert.New(t) 187 | 188 | chWithValue := make(chan struct{}, 1) 189 | chWithValue <- struct{}{} 190 | var tiP *time.Time 191 | var tiNP time.Time 192 | var s *string 193 | var f *os.File 194 | ptrs := new(string) 195 | *ptrs = "" 196 | 197 | is.True(IsEmpty(ptrs), "Nil string pointer is empty") 198 | is.True(IsEmpty(""), "Empty string is empty") 199 | is.True(IsEmpty(nil), "Nil is empty") 200 | is.True(IsEmpty([]string{}), "Empty string array is empty") 201 | is.True(IsEmpty(0), "Zero int value is empty") 202 | is.True(IsEmpty(false), "False value is empty") 203 | is.True(IsEmpty(make(chan struct{})), "Channel without values is empty") 204 | is.True(IsEmpty(s), "Nil string pointer is empty") 205 | is.True(IsEmpty(f), "Nil os.File pointer is empty") 206 | is.True(IsEmpty(tiP), "Nil time.Time pointer is empty") 207 | is.True(IsEmpty(tiNP), "time.Time is empty") 208 | 209 | is.False(NotEmpty(ptrs), "Nil string pointer is empty") 210 | is.False(NotEmpty(""), "Empty string is empty") 211 | is.False(NotEmpty(nil), "Nil is empty") 212 | is.False(NotEmpty([]string{}), "Empty string array is empty") 213 | is.False(NotEmpty(0), "Zero int value is empty") 214 | is.False(NotEmpty(false), "False value is empty") 215 | is.False(NotEmpty(make(chan struct{})), "Channel without values is empty") 216 | is.False(NotEmpty(s), "Nil string pointer is empty") 217 | is.False(NotEmpty(f), "Nil os.File pointer is empty") 218 | is.False(NotEmpty(tiP), "Nil time.Time pointer is empty") 219 | is.False(NotEmpty(tiNP), "time.Time is empty") 220 | 221 | is.False(IsEmpty("something"), "Non Empty string is not empty") 222 | is.False(IsEmpty(errors.New("something")), "Non nil object is not empty") 223 | is.False(IsEmpty([]string{"something"}), "Non empty string array is not empty") 224 | is.False(IsEmpty(1), "Non-zero int value is not empty") 225 | is.False(IsEmpty(true), "True value is not empty") 226 | is.False(IsEmpty(chWithValue), "Channel with values is not empty") 227 | 228 | is.True(NotEmpty("something"), "Non Empty string is not empty") 229 | is.True(NotEmpty(errors.New("something")), "Non nil object is not empty") 230 | is.True(NotEmpty([]string{"something"}), "Non empty string array is not empty") 231 | is.True(NotEmpty(1), "Non-zero int value is not empty") 232 | is.True(NotEmpty(true), "True value is not empty") 233 | is.True(NotEmpty(chWithValue), "Channel with values is not empty") 234 | } 235 | 236 | func TestIsZero(t *testing.T) { 237 | is := assert.New(t) 238 | 239 | for _, test := range zeros { 240 | is.True(IsZero(test)) 241 | } 242 | 243 | for _, test := range nonZeros { 244 | is.False(IsZero(test)) 245 | } 246 | } 247 | 248 | func TestAny(t *testing.T) { 249 | is := assert.New(t) 250 | 251 | is.True(Any(true, false)) 252 | is.True(Any(true, true)) 253 | is.False(Any(false, false)) 254 | is.False(Any("", nil, false)) 255 | } 256 | 257 | func TestAll(t *testing.T) { 258 | is := assert.New(t) 259 | 260 | is.False(All(true, false)) 261 | is.True(All(true, true)) 262 | is.False(All(false, false)) 263 | is.False(All("", nil, false)) 264 | is.True(All("foo", true, 3)) 265 | } 266 | 267 | func TestIsIteratee(t *testing.T) { 268 | is := assert.New(t) 269 | 270 | is.False(IsIteratee(nil)) 271 | } 272 | 273 | func TestIsFunction(t *testing.T) { 274 | is := assert.New(t) 275 | 276 | is.False(IsFunction(nil)) 277 | is.False(IsFunction("")) 278 | is.True(IsFunction(func() {})) 279 | is.True(IsFunction(func(string, string, string) bool { return false }, 3)) 280 | is.False(IsFunction(func(string, string, string) bool { return false }, 3, 0)) 281 | is.True(IsFunction(func(string, string, string) (bool, error) { return false, nil }, 3, 2)) 282 | } 283 | 284 | func TestIsPredicate(t *testing.T) { 285 | is := assert.New(t) 286 | 287 | is.False(IsPredicate(nil)) 288 | is.False(IsPredicate("")) 289 | is.False(IsPredicate(func() {})) 290 | is.False(IsPredicate(func() bool { return false})) 291 | is.True(IsPredicate(func(int) bool { return false})) 292 | is.True(IsPredicate(func(int) bool { return false}, nil)) 293 | is.False(IsPredicate(func(int) bool { return false}, reflect.TypeOf(""))) 294 | is.True(IsPredicate(func(int) bool { return false}, reflect.TypeOf(0))) 295 | is.False(IsPredicate(func(int, string) bool { return false}, reflect.TypeOf(""))) 296 | is.False(IsPredicate(func(int, string) bool { return false}, reflect.TypeOf(""), reflect.TypeOf(0))) 297 | is.True(IsPredicate(func(int, string) bool { return false}, reflect.TypeOf(0), reflect.TypeOf(""))) 298 | is.False(IsPredicate(func(struct{}, string) bool { return false}, reflect.TypeOf(0), reflect.TypeOf(""))) 299 | is.True(IsPredicate(func(struct{}, string) bool { return false}, nil, reflect.TypeOf(""))) 300 | } 301 | -------------------------------------------------------------------------------- /intersection.go: -------------------------------------------------------------------------------- 1 | package funk 2 | 3 | import ( 4 | "reflect" 5 | ) 6 | 7 | // Intersect returns the intersection between two collections. 8 | // 9 | // Deprecated: use Join(x, y, InnerJoin) instead of Intersect, InnerJoin 10 | // implements deduplication mechanism, so verify your code behaviour 11 | // before using it 12 | func Intersect(x interface{}, y interface{}) interface{} { 13 | if !IsCollection(x) { 14 | panic("First parameter must be a collection") 15 | } 16 | if !IsCollection(y) { 17 | panic("Second parameter must be a collection") 18 | } 19 | 20 | hash := map[interface{}]struct{}{} 21 | 22 | xValue := reflect.ValueOf(x) 23 | xType := xValue.Type() 24 | 25 | yValue := reflect.ValueOf(y) 26 | yType := yValue.Type() 27 | 28 | if NotEqual(xType, yType) { 29 | panic("Parameters must have the same type") 30 | } 31 | 32 | zType := reflect.SliceOf(xType.Elem()) 33 | zSlice := reflect.MakeSlice(zType, 0, 0) 34 | 35 | for i := 0; i < xValue.Len(); i++ { 36 | v := xValue.Index(i).Interface() 37 | hash[v] = struct{}{} 38 | } 39 | 40 | for i := 0; i < yValue.Len(); i++ { 41 | v := yValue.Index(i).Interface() 42 | _, ok := hash[v] 43 | if ok { 44 | zSlice = reflect.Append(zSlice, yValue.Index(i)) 45 | } 46 | } 47 | 48 | return zSlice.Interface() 49 | } 50 | 51 | // IntersectString returns the intersection between two collections of string. 52 | func IntersectString(x []string, y []string) []string { 53 | if len(x) == 0 || len(y) == 0 { 54 | return []string{} 55 | } 56 | 57 | set := []string{} 58 | hash := map[string]struct{}{} 59 | 60 | for _, v := range x { 61 | hash[v] = struct{}{} 62 | } 63 | 64 | for _, v := range y { 65 | _, ok := hash[v] 66 | if ok { 67 | set = append(set, v) 68 | } 69 | } 70 | 71 | return set 72 | } 73 | 74 | // Difference returns the difference between two collections. 75 | func Difference(x interface{}, y interface{}) (interface{}, interface{}) { 76 | if !IsIteratee(x) { 77 | panic("First parameter must be an iteratee") 78 | } 79 | if !IsIteratee(y) { 80 | panic("Second parameter must be an iteratee") 81 | } 82 | 83 | xValue := reflect.ValueOf(x) 84 | xType := xValue.Type() 85 | 86 | yValue := reflect.ValueOf(y) 87 | yType := yValue.Type() 88 | 89 | if NotEqual(xType, yType) { 90 | panic("Parameters must have the same type") 91 | } 92 | 93 | if xType.Kind() == reflect.Map { 94 | leftType := reflect.MapOf(xType.Key(), xType.Elem()) 95 | rightType := reflect.MapOf(xType.Key(), xType.Elem()) 96 | leftMap := reflect.MakeMap(leftType) 97 | rightMap := reflect.MakeMap(rightType) 98 | 99 | xIter := xValue.MapRange() 100 | for xIter.Next() { 101 | k := xIter.Key() 102 | xv := xIter.Value() 103 | yv := yValue.MapIndex(k) 104 | equalTo := equal(xv.Interface(), true) 105 | if !yv.IsValid() || !equalTo(yv, yv) { 106 | leftMap.SetMapIndex(k, xv) 107 | } 108 | } 109 | 110 | yIter := yValue.MapRange() 111 | for yIter.Next() { 112 | k := yIter.Key() 113 | yv := yIter.Value() 114 | xv := xValue.MapIndex(k) 115 | equalTo := equal(yv.Interface(), true) 116 | if !xv.IsValid() || !equalTo(xv, xv) { 117 | rightMap.SetMapIndex(k, yv) 118 | } 119 | } 120 | return leftMap.Interface(), rightMap.Interface() 121 | } else { 122 | leftType := reflect.SliceOf(xType.Elem()) 123 | rightType := reflect.SliceOf(yType.Elem()) 124 | leftSlice := reflect.MakeSlice(leftType, 0, 0) 125 | rightSlice := reflect.MakeSlice(rightType, 0, 0) 126 | 127 | for i := 0; i < xValue.Len(); i++ { 128 | v := xValue.Index(i).Interface() 129 | if !Contains(y, v) { 130 | leftSlice = reflect.Append(leftSlice, xValue.Index(i)) 131 | } 132 | } 133 | 134 | for i := 0; i < yValue.Len(); i++ { 135 | v := yValue.Index(i).Interface() 136 | if !Contains(x, v) { 137 | rightSlice = reflect.Append(rightSlice, yValue.Index(i)) 138 | } 139 | } 140 | return leftSlice.Interface(), rightSlice.Interface() 141 | } 142 | } 143 | 144 | // DifferenceString returns the difference between two collections of strings. 145 | func DifferenceString(x []string, y []string) ([]string, []string) { 146 | leftSlice := []string{} 147 | rightSlice := []string{} 148 | 149 | for _, v := range x { 150 | if !ContainsString(y, v) { 151 | leftSlice = append(leftSlice, v) 152 | } 153 | } 154 | 155 | for _, v := range y { 156 | if !ContainsString(x, v) { 157 | rightSlice = append(rightSlice, v) 158 | } 159 | } 160 | 161 | return leftSlice, rightSlice 162 | } 163 | 164 | // DifferenceInt64 returns the difference between two collections of int64s. 165 | func DifferenceInt64(x []int64, y []int64) ([]int64, []int64) { 166 | leftSlice := []int64{} 167 | rightSlice := []int64{} 168 | 169 | for _, v := range x { 170 | if !ContainsInt64(y, v) { 171 | leftSlice = append(leftSlice, v) 172 | } 173 | } 174 | 175 | for _, v := range y { 176 | if !ContainsInt64(x, v) { 177 | rightSlice = append(rightSlice, v) 178 | } 179 | } 180 | 181 | return leftSlice, rightSlice 182 | } 183 | 184 | // DifferenceInt32 returns the difference between two collections of ints32. 185 | func DifferenceInt32(x []int32, y []int32) ([]int32, []int32) { 186 | leftSlice := []int32{} 187 | rightSlice := []int32{} 188 | 189 | for _, v := range x { 190 | if !ContainsInt32(y, v) { 191 | leftSlice = append(leftSlice, v) 192 | } 193 | } 194 | 195 | for _, v := range y { 196 | if !ContainsInt32(x, v) { 197 | rightSlice = append(rightSlice, v) 198 | } 199 | } 200 | 201 | return leftSlice, rightSlice 202 | } 203 | 204 | // DifferenceInt returns the difference between two collections of ints. 205 | func DifferenceInt(x []int, y []int) ([]int, []int) { 206 | leftSlice := []int{} 207 | rightSlice := []int{} 208 | 209 | for _, v := range x { 210 | if !ContainsInt(y, v) { 211 | leftSlice = append(leftSlice, v) 212 | } 213 | } 214 | 215 | for _, v := range y { 216 | if !ContainsInt(x, v) { 217 | rightSlice = append(rightSlice, v) 218 | } 219 | } 220 | 221 | return leftSlice, rightSlice 222 | } 223 | 224 | // DifferenceUInt returns the difference between two collections of uints. 225 | func DifferenceUInt(x []uint, y []uint) ([]uint, []uint) { 226 | leftSlice := []uint{} 227 | rightSlice := []uint{} 228 | 229 | for _, v := range x { 230 | if !ContainsUInt(y, v) { 231 | leftSlice = append(leftSlice, v) 232 | } 233 | } 234 | 235 | for _, v := range y { 236 | if !ContainsUInt(x, v) { 237 | rightSlice = append(rightSlice, v) 238 | } 239 | } 240 | 241 | return leftSlice, rightSlice 242 | } 243 | 244 | // DifferenceUInt32 returns the difference between two collections of uints32. 245 | func DifferenceUInt32(x []uint32, y []uint32) ([]uint32, []uint32) { 246 | leftSlice := []uint32{} 247 | rightSlice := []uint32{} 248 | 249 | for _, v := range x { 250 | if !ContainsUInt32(y, v) { 251 | leftSlice = append(leftSlice, v) 252 | } 253 | } 254 | 255 | for _, v := range y { 256 | if !ContainsUInt32(x, v) { 257 | rightSlice = append(rightSlice, v) 258 | } 259 | } 260 | 261 | return leftSlice, rightSlice 262 | } 263 | 264 | // DifferenceUInt64 returns the difference between two collections of uints64. 265 | func DifferenceUInt64(x []uint64, y []uint64) ([]uint64, []uint64) { 266 | leftSlice := []uint64{} 267 | rightSlice := []uint64{} 268 | 269 | for _, v := range x { 270 | if !ContainsUInt64(y, v) { 271 | leftSlice = append(leftSlice, v) 272 | } 273 | } 274 | 275 | for _, v := range y { 276 | if !ContainsUInt64(x, v) { 277 | rightSlice = append(rightSlice, v) 278 | } 279 | } 280 | 281 | return leftSlice, rightSlice 282 | } 283 | -------------------------------------------------------------------------------- /intersection_test.go: -------------------------------------------------------------------------------- 1 | package funk 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestIntersect(t *testing.T) { 10 | is := assert.New(t) 11 | 12 | r := Intersect([]int{1, 2, 3, 4}, []int{2, 4, 6}) 13 | is.Equal(r, []int{2, 4}) 14 | 15 | r = Intersect([]string{"foo", "bar", "hello", "bar"}, []string{"foo", "bar"}) 16 | is.Equal(r, []string{"foo", "bar"}) 17 | 18 | r = Intersect([]string{"foo", "bar"}, []string{"foo", "bar", "hello", "bar"}) 19 | is.Equal(r, []string{"foo", "bar", "bar"}) 20 | } 21 | 22 | func TestIntersectString(t *testing.T) { 23 | is := assert.New(t) 24 | 25 | r := IntersectString([]string{"foo", "bar", "hello", "bar"}, []string{"foo", "bar"}) 26 | is.Equal(r, []string{"foo", "bar"}) 27 | } 28 | 29 | func TestDifference(t *testing.T) { 30 | is := assert.New(t) 31 | 32 | r1, r2 := Difference([]int{1, 2, 3, 4}, []int{2, 4, 6}) 33 | is.Equal(r1, []int{1, 3}) 34 | is.Equal(r2, []int{6}) 35 | 36 | r1, r2 = Difference([]string{"foo", "bar", "hello", "bar"}, []string{"foo", "bar"}) 37 | is.Equal(r1, []string{"hello"}) 38 | is.Equal(r2, []string{}) 39 | 40 | r1, r2 = Difference(map[string]string{"foo": "bar", "hello": "baz"}, map[string]string{"foo": "bar", "bar": "baz"}) 41 | is.Equal(r1, map[string]string{"hello": "baz"}) 42 | is.Equal(r2, map[string]string{"bar": "baz"}) 43 | } 44 | 45 | func TestDifferenceString(t *testing.T) { 46 | is := assert.New(t) 47 | 48 | r1, r2 := DifferenceString([]string{"foo", "bar", "hello", "bar"}, []string{"foo", "bar"}) 49 | is.Equal(r1, []string{"hello"}) 50 | is.Equal(r2, []string{}) 51 | } 52 | 53 | func TestDifferenceInt64(t *testing.T) { 54 | is := assert.New(t) 55 | 56 | r1, r2 := DifferenceInt64([]int64{1, 2, 3, 4}, []int64{4, 6}) 57 | is.Equal(r1, []int64{1, 2, 3}) 58 | is.Equal(r2, []int64{6}) 59 | } 60 | -------------------------------------------------------------------------------- /join.go: -------------------------------------------------------------------------------- 1 | package funk 2 | 3 | import ( 4 | "reflect" 5 | "strings" 6 | ) 7 | 8 | type JoinFnc func(lx, rx reflect.Value) reflect.Value 9 | 10 | // Join combines two collections using the given join method. 11 | func Join(larr, rarr interface{}, fnc JoinFnc) interface{} { 12 | if !IsCollection(larr) { 13 | panic("First parameter must be a collection") 14 | } 15 | if !IsCollection(rarr) { 16 | panic("Second parameter must be a collection") 17 | } 18 | 19 | lvalue := reflect.ValueOf(larr) 20 | rvalue := reflect.ValueOf(rarr) 21 | if NotEqual(lvalue.Type(), rvalue.Type()) { 22 | panic("Parameters must have the same type") 23 | } 24 | 25 | return fnc(lvalue, rvalue).Interface() 26 | } 27 | 28 | // InnerJoin finds and returns matching data from two collections. 29 | func InnerJoin(lx, rx reflect.Value) reflect.Value { 30 | result := reflect.MakeSlice(reflect.SliceOf(lx.Type().Elem()), 0, lx.Len()+rx.Len()) 31 | rhash := hashSlice(rx) 32 | lhash := make(map[interface{}]struct{}, lx.Len()) 33 | 34 | for i := 0; i < lx.Len(); i++ { 35 | v := lx.Index(i) 36 | _, ok := rhash[v.Interface()] 37 | _, alreadyExists := lhash[v.Interface()] 38 | if ok && !alreadyExists { 39 | lhash[v.Interface()] = struct{}{} 40 | result = reflect.Append(result, v) 41 | } 42 | } 43 | return result 44 | } 45 | 46 | // OuterJoin finds and returns dissimilar data from two collections. 47 | func OuterJoin(lx, rx reflect.Value) reflect.Value { 48 | ljoin := LeftJoin(lx, rx) 49 | rjoin := RightJoin(lx, rx) 50 | 51 | result := reflect.MakeSlice(reflect.SliceOf(lx.Type().Elem()), ljoin.Len()+rjoin.Len(), ljoin.Len()+rjoin.Len()) 52 | for i := 0; i < ljoin.Len(); i++ { 53 | result.Index(i).Set(ljoin.Index(i)) 54 | } 55 | for i := 0; i < rjoin.Len(); i++ { 56 | result.Index(ljoin.Len() + i).Set(rjoin.Index(i)) 57 | } 58 | 59 | return result 60 | } 61 | 62 | // LeftJoin finds and returns dissimilar data from the first collection (left). 63 | func LeftJoin(lx, rx reflect.Value) reflect.Value { 64 | result := reflect.MakeSlice(reflect.SliceOf(lx.Type().Elem()), 0, lx.Len()) 65 | rhash := hashSlice(rx) 66 | 67 | for i := 0; i < lx.Len(); i++ { 68 | v := lx.Index(i) 69 | _, ok := rhash[v.Interface()] 70 | if !ok { 71 | result = reflect.Append(result, v) 72 | } 73 | } 74 | return result 75 | } 76 | 77 | // LeftJoin finds and returns dissimilar data from the second collection (right). 78 | func RightJoin(lx, rx reflect.Value) reflect.Value { return LeftJoin(rx, lx) } 79 | 80 | func hashSlice(arr reflect.Value) map[interface{}]struct{} { 81 | hash := map[interface{}]struct{}{} 82 | for i := 0; i < arr.Len(); i++ { 83 | v := arr.Index(i).Interface() 84 | hash[v] = struct{}{} 85 | } 86 | return hash 87 | } 88 | 89 | // StringerJoin joins an array of elements which implement the `String() string` function. 90 | // Direct copy of strings.Join() with a few tweaks. 91 | func StringerJoin(elems []interface{ String() string }, sep string) string { 92 | switch len(elems) { 93 | case 0: 94 | return "" 95 | case 1: 96 | return elems[0].String() 97 | } 98 | n := len(sep) * (len(elems) - 1) 99 | for i := 0; i < len(elems); i++ { 100 | n += len(elems[i].String()) 101 | } 102 | 103 | var b strings.Builder 104 | b.Grow(n) 105 | b.WriteString(elems[0].String()) 106 | for _, s := range elems[1:] { 107 | b.WriteString(sep) 108 | b.WriteString(s.String()) 109 | } 110 | return b.String() 111 | } 112 | -------------------------------------------------------------------------------- /join_primitives.go: -------------------------------------------------------------------------------- 1 | package funk 2 | 3 | type JoinIntFnc func(lx, rx []int) []int 4 | 5 | // JoinInt combines two int collections using the given join method. 6 | func JoinInt(larr, rarr []int, fnc JoinIntFnc) []int { 7 | return fnc(larr, rarr) 8 | } 9 | 10 | // InnerJoinInt finds and returns matching data from two int collections. 11 | func InnerJoinInt(lx, rx []int) []int { 12 | result := make([]int, 0, len(lx)+len(rx)) 13 | rhash := hashSliceInt(rx) 14 | lhash := make(map[int]struct{}, len(lx)) 15 | 16 | for _, v := range lx { 17 | _, ok := rhash[v] 18 | _, alreadyExists := lhash[v] 19 | if ok && !alreadyExists { 20 | lhash[v] = struct{}{} 21 | result = append(result, v) 22 | } 23 | } 24 | return result 25 | } 26 | 27 | // OuterJoinInt finds and returns dissimilar data from two int collections. 28 | func OuterJoinInt(lx, rx []int) []int { 29 | ljoin := LeftJoinInt(lx, rx) 30 | rjoin := RightJoinInt(lx, rx) 31 | 32 | result := make([]int, len(ljoin)+len(rjoin)) 33 | copy(result, ljoin) 34 | for i, v := range rjoin { 35 | result[len(ljoin)+i] = v 36 | } 37 | return result 38 | } 39 | 40 | // LeftJoinInt finds and returns dissimilar data from the first int collection (left). 41 | func LeftJoinInt(lx, rx []int) []int { 42 | result := make([]int, 0, len(lx)) 43 | rhash := hashSliceInt(rx) 44 | 45 | for _, v := range lx { 46 | _, ok := rhash[v] 47 | if !ok { 48 | result = append(result, v) 49 | } 50 | } 51 | return result 52 | } 53 | 54 | // LeftJoinInt finds and returns dissimilar data from the second int collection (right). 55 | func RightJoinInt(lx, rx []int) []int { return LeftJoinInt(rx, lx) } 56 | 57 | func hashSliceInt(arr []int) map[int]struct{} { 58 | hash := make(map[int]struct{}, len(arr)) 59 | for _, i := range arr { 60 | hash[i] = struct{}{} 61 | } 62 | return hash 63 | } 64 | 65 | type JoinInt32Fnc func(lx, rx []int32) []int32 66 | 67 | // JoinInt32 combines two int32 collections using the given join method. 68 | func JoinInt32(larr, rarr []int32, fnc JoinInt32Fnc) []int32 { 69 | return fnc(larr, rarr) 70 | } 71 | 72 | // InnerJoinInt32 finds and returns matching data from two int32 collections. 73 | func InnerJoinInt32(lx, rx []int32) []int32 { 74 | result := make([]int32, 0, len(lx)+len(rx)) 75 | rhash := hashSliceInt32(rx) 76 | lhash := make(map[int32]struct{}, len(lx)) 77 | 78 | for _, v := range lx { 79 | _, ok := rhash[v] 80 | _, alreadyExists := lhash[v] 81 | if ok && !alreadyExists { 82 | lhash[v] = struct{}{} 83 | result = append(result, v) 84 | } 85 | } 86 | return result 87 | } 88 | 89 | // OuterJoinInt32 finds and returns dissimilar data from two int32 collections. 90 | func OuterJoinInt32(lx, rx []int32) []int32 { 91 | ljoin := LeftJoinInt32(lx, rx) 92 | rjoin := RightJoinInt32(lx, rx) 93 | 94 | result := make([]int32, len(ljoin)+len(rjoin)) 95 | copy(result, ljoin) 96 | for i, v := range rjoin { 97 | result[len(ljoin)+i] = v 98 | } 99 | return result 100 | } 101 | 102 | // LeftJoinInt32 finds and returns dissimilar data from the first int32 collection (left). 103 | func LeftJoinInt32(lx, rx []int32) []int32 { 104 | result := make([]int32, 0, len(lx)) 105 | rhash := hashSliceInt32(rx) 106 | 107 | for _, v := range lx { 108 | _, ok := rhash[v] 109 | if !ok { 110 | result = append(result, v) 111 | } 112 | } 113 | return result 114 | } 115 | 116 | // LeftJoinInt32 finds and returns dissimilar data from the second int32 collection (right). 117 | func RightJoinInt32(lx, rx []int32) []int32 { return LeftJoinInt32(rx, lx) } 118 | 119 | func hashSliceInt32(arr []int32) map[int32]struct{} { 120 | hash := make(map[int32]struct{}, len(arr)) 121 | for _, i := range arr { 122 | hash[i] = struct{}{} 123 | } 124 | return hash 125 | } 126 | 127 | type JoinInt64Fnc func(lx, rx []int64) []int64 128 | 129 | // JoinInt64 combines two int64 collections using the given join method. 130 | func JoinInt64(larr, rarr []int64, fnc JoinInt64Fnc) []int64 { 131 | return fnc(larr, rarr) 132 | } 133 | 134 | // InnerJoinInt64 finds and returns matching data from two int64 collections. 135 | func InnerJoinInt64(lx, rx []int64) []int64 { 136 | result := make([]int64, 0, len(lx)+len(rx)) 137 | rhash := hashSliceInt64(rx) 138 | lhash := make(map[int64]struct{}, len(lx)) 139 | 140 | for _, v := range lx { 141 | _, ok := rhash[v] 142 | _, alreadyExists := lhash[v] 143 | if ok && !alreadyExists { 144 | lhash[v] = struct{}{} 145 | result = append(result, v) 146 | } 147 | } 148 | return result 149 | } 150 | 151 | // OuterJoinInt64 finds and returns dissimilar data from two int64 collections. 152 | func OuterJoinInt64(lx, rx []int64) []int64 { 153 | ljoin := LeftJoinInt64(lx, rx) 154 | rjoin := RightJoinInt64(lx, rx) 155 | 156 | result := make([]int64, len(ljoin)+len(rjoin)) 157 | copy(result, ljoin) 158 | for i, v := range rjoin { 159 | result[len(ljoin)+i] = v 160 | } 161 | return result 162 | } 163 | 164 | // LeftJoinInt64 finds and returns dissimilar data from the first int64 collection (left). 165 | func LeftJoinInt64(lx, rx []int64) []int64 { 166 | result := make([]int64, 0, len(lx)) 167 | rhash := hashSliceInt64(rx) 168 | 169 | for _, v := range lx { 170 | _, ok := rhash[v] 171 | if !ok { 172 | result = append(result, v) 173 | } 174 | } 175 | return result 176 | } 177 | 178 | // LeftJoinInt64 finds and returns dissimilar data from the second int64 collection (right). 179 | func RightJoinInt64(lx, rx []int64) []int64 { return LeftJoinInt64(rx, lx) } 180 | 181 | func hashSliceInt64(arr []int64) map[int64]struct{} { 182 | hash := make(map[int64]struct{}, len(arr)) 183 | for _, i := range arr { 184 | hash[i] = struct{}{} 185 | } 186 | return hash 187 | } 188 | 189 | type JoinStringFnc func(lx, rx []string) []string 190 | 191 | // JoinString combines two string collections using the given join method. 192 | func JoinString(larr, rarr []string, fnc JoinStringFnc) []string { 193 | return fnc(larr, rarr) 194 | } 195 | 196 | // InnerJoinString finds and returns matching data from two string collections. 197 | func InnerJoinString(lx, rx []string) []string { 198 | result := make([]string, 0, len(lx)+len(rx)) 199 | rhash := hashSliceString(rx) 200 | lhash := make(map[string]struct{}, len(lx)) 201 | 202 | for _, v := range lx { 203 | _, ok := rhash[v] 204 | _, alreadyExists := lhash[v] 205 | if ok && !alreadyExists { 206 | lhash[v] = struct{}{} 207 | result = append(result, v) 208 | } 209 | } 210 | return result 211 | } 212 | 213 | // OuterJoinString finds and returns dissimilar data from two string collections. 214 | func OuterJoinString(lx, rx []string) []string { 215 | ljoin := LeftJoinString(lx, rx) 216 | rjoin := RightJoinString(lx, rx) 217 | 218 | result := make([]string, len(ljoin)+len(rjoin)) 219 | copy(result, ljoin) 220 | for i, v := range rjoin { 221 | result[len(ljoin)+i] = v 222 | } 223 | return result 224 | } 225 | 226 | // LeftJoinString finds and returns dissimilar data from the first string collection (left). 227 | func LeftJoinString(lx, rx []string) []string { 228 | result := make([]string, 0, len(lx)) 229 | rhash := hashSliceString(rx) 230 | 231 | for _, v := range lx { 232 | _, ok := rhash[v] 233 | if !ok { 234 | result = append(result, v) 235 | } 236 | } 237 | return result 238 | } 239 | 240 | // LeftJoinString finds and returns dissimilar data from the second string collection (right). 241 | func RightJoinString(lx, rx []string) []string { return LeftJoinString(rx, lx) } 242 | 243 | func hashSliceString(arr []string) map[string]struct{} { 244 | hash := make(map[string]struct{}, len(arr)) 245 | for _, i := range arr { 246 | hash[i] = struct{}{} 247 | } 248 | return hash 249 | } 250 | 251 | type JoinFloat32Fnc func(lx, rx []float32) []float32 252 | 253 | // JoinFloat32 combines two float32 collections using the given join method. 254 | func JoinFloat32(larr, rarr []float32, fnc JoinFloat32Fnc) []float32 { 255 | return fnc(larr, rarr) 256 | } 257 | 258 | // InnerJoinFloat32 finds and returns matching data from two float32 collections. 259 | func InnerJoinFloat32(lx, rx []float32) []float32 { 260 | result := make([]float32, 0, len(lx)+len(rx)) 261 | rhash := hashSliceFloat32(rx) 262 | lhash := make(map[float32]struct{}, len(lx)) 263 | 264 | for _, v := range lx { 265 | _, ok := rhash[v] 266 | _, alreadyExists := lhash[v] 267 | if ok && !alreadyExists { 268 | lhash[v] = struct{}{} 269 | result = append(result, v) 270 | } 271 | } 272 | return result 273 | } 274 | 275 | // OuterJoinFloat32 finds and returns dissimilar data from two float32 collections. 276 | func OuterJoinFloat32(lx, rx []float32) []float32 { 277 | ljoin := LeftJoinFloat32(lx, rx) 278 | rjoin := RightJoinFloat32(lx, rx) 279 | 280 | result := make([]float32, len(ljoin)+len(rjoin)) 281 | copy(result, ljoin) 282 | for i, v := range rjoin { 283 | result[len(ljoin)+i] = v 284 | } 285 | return result 286 | } 287 | 288 | // LeftJoinFloat32 finds and returns dissimilar data from the first float32 collection (left). 289 | func LeftJoinFloat32(lx, rx []float32) []float32 { 290 | result := make([]float32, 0, len(lx)) 291 | rhash := hashSliceFloat32(rx) 292 | 293 | for _, v := range lx { 294 | _, ok := rhash[v] 295 | if !ok { 296 | result = append(result, v) 297 | } 298 | } 299 | return result 300 | } 301 | 302 | // LeftJoinFloat32 finds and returns dissimilar data from the second float32 collection (right). 303 | func RightJoinFloat32(lx, rx []float32) []float32 { return LeftJoinFloat32(rx, lx) } 304 | 305 | func hashSliceFloat32(arr []float32) map[float32]struct{} { 306 | hash := make(map[float32]struct{}, len(arr)) 307 | for _, i := range arr { 308 | hash[i] = struct{}{} 309 | } 310 | return hash 311 | } 312 | 313 | type JoinFloat64Fnc func(lx, rx []float64) []float64 314 | 315 | // JoinFloat64 combines two float64 collections using the given join method. 316 | func JoinFloat64(larr, rarr []float64, fnc JoinFloat64Fnc) []float64 { 317 | return fnc(larr, rarr) 318 | } 319 | 320 | // InnerJoinFloat64 finds and returns matching data from two float64 collections. 321 | func InnerJoinFloat64(lx, rx []float64) []float64 { 322 | result := make([]float64, 0, len(lx)+len(rx)) 323 | rhash := hashSliceFloat64(rx) 324 | lhash := make(map[float64]struct{}, len(lx)) 325 | 326 | for _, v := range lx { 327 | _, ok := rhash[v] 328 | _, alreadyExists := lhash[v] 329 | if ok && !alreadyExists { 330 | lhash[v] = struct{}{} 331 | result = append(result, v) 332 | } 333 | } 334 | return result 335 | } 336 | 337 | // OuterJoinFloat64 finds and returns dissimilar data from two float64 collections. 338 | func OuterJoinFloat64(lx, rx []float64) []float64 { 339 | ljoin := LeftJoinFloat64(lx, rx) 340 | rjoin := RightJoinFloat64(lx, rx) 341 | 342 | result := make([]float64, len(ljoin)+len(rjoin)) 343 | copy(result, ljoin) 344 | for i, v := range rjoin { 345 | result[len(ljoin)+i] = v 346 | } 347 | return result 348 | } 349 | 350 | // LeftJoinFloat64 finds and returns dissimilar data from the first float64 collection (left). 351 | func LeftJoinFloat64(lx, rx []float64) []float64 { 352 | result := make([]float64, 0, len(lx)) 353 | rhash := hashSliceFloat64(rx) 354 | 355 | for _, v := range lx { 356 | _, ok := rhash[v] 357 | if !ok { 358 | result = append(result, v) 359 | } 360 | } 361 | return result 362 | } 363 | 364 | // LeftJoinFloat64 finds and returns dissimilar data from the second float64 collection (right). 365 | func RightJoinFloat64(lx, rx []float64) []float64 { return LeftJoinFloat64(rx, lx) } 366 | 367 | func hashSliceFloat64(arr []float64) map[float64]struct{} { 368 | hash := make(map[float64]struct{}, len(arr)) 369 | for _, i := range arr { 370 | hash[i] = struct{}{} 371 | } 372 | return hash 373 | } 374 | -------------------------------------------------------------------------------- /join_test.go: -------------------------------------------------------------------------------- 1 | package funk 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestJoin_InnerJoin(t *testing.T) { 11 | testCases := []struct { 12 | LeftArr interface{} 13 | RightArr interface{} 14 | Expect interface{} 15 | }{ 16 | {[]string{"foo", "bar"}, []string{"bar", "baz"}, []string{"bar"}}, 17 | {[]string{"foo", "bar", "bar"}, []string{"bar", "baz"}, []string{"bar"}}, 18 | {[]string{"foo", "bar"}, []string{"bar", "bar", "baz"}, []string{"bar"}}, 19 | {[]string{"foo", "bar", "bar"}, []string{"bar", "bar", "baz"}, []string{"bar"}}, 20 | {[]int{0, 1, 2, 3, 4}, []int{3, 4, 5, 6, 7}, []int{3, 4}}, 21 | {[]*Foo{f, b}, []*Foo{b, c}, []*Foo{b}}, 22 | } 23 | 24 | for idx, tt := range testCases { 25 | t.Run(fmt.Sprintf("test case #%d", idx+1), func(t *testing.T) { 26 | is := assert.New(t) 27 | 28 | actual := Join(tt.LeftArr, tt.RightArr, InnerJoin) 29 | is.Equal(tt.Expect, actual) 30 | }) 31 | } 32 | } 33 | 34 | func TestJoin_OuterJoin(t *testing.T) { 35 | testCases := []struct { 36 | LeftArr interface{} 37 | RightArr interface{} 38 | Expect interface{} 39 | }{ 40 | {[]string{"foo", "bar"}, []string{"bar", "baz"}, []string{"foo", "baz"}}, 41 | {[]int{0, 1, 2, 3, 4}, []int{3, 4, 5, 6, 7}, []int{0, 1, 2, 5, 6, 7}}, 42 | {[]*Foo{f, b}, []*Foo{b, c}, []*Foo{f, c}}, 43 | } 44 | 45 | for idx, tt := range testCases { 46 | t.Run(fmt.Sprintf("test case #%d", idx+1), func(t *testing.T) { 47 | is := assert.New(t) 48 | 49 | actual := Join(tt.LeftArr, tt.RightArr, OuterJoin) 50 | is.Equal(tt.Expect, actual) 51 | }) 52 | } 53 | } 54 | 55 | func TestJoin_LeftJoin(t *testing.T) { 56 | testCases := []struct { 57 | LeftArr interface{} 58 | RightArr interface{} 59 | Expect interface{} 60 | }{ 61 | {[]string{"foo", "bar"}, []string{"bar", "baz"}, []string{"foo"}}, 62 | {[]int{0, 1, 2, 3, 4}, []int{3, 4, 5, 6, 7}, []int{0, 1, 2}}, 63 | {[]*Foo{f, b}, []*Foo{b, c}, []*Foo{f}}, 64 | } 65 | 66 | for idx, tt := range testCases { 67 | t.Run(fmt.Sprintf("test case #%d", idx+1), func(t *testing.T) { 68 | is := assert.New(t) 69 | 70 | actual := Join(tt.LeftArr, tt.RightArr, LeftJoin) 71 | is.Equal(tt.Expect, actual) 72 | }) 73 | } 74 | } 75 | 76 | func TestJoin_RightJoin(t *testing.T) { 77 | testCases := []struct { 78 | LeftArr interface{} 79 | RightArr interface{} 80 | Expect interface{} 81 | }{ 82 | {[]string{"foo", "bar"}, []string{"bar", "baz"}, []string{"baz"}}, 83 | {[]int{0, 1, 2, 3, 4}, []int{3, 4, 5, 6, 7}, []int{5, 6, 7}}, 84 | {[]*Foo{f, b}, []*Foo{b, c}, []*Foo{c}}, 85 | } 86 | 87 | for idx, tt := range testCases { 88 | t.Run(fmt.Sprintf("test case #%d", idx+1), func(t *testing.T) { 89 | is := assert.New(t) 90 | 91 | actual := Join(tt.LeftArr, tt.RightArr, RightJoin) 92 | is.Equal(tt.Expect, actual) 93 | }) 94 | } 95 | } 96 | 97 | // Struct which implements the String() method to test StringerJoin(). 98 | type S struct { 99 | Value string 100 | } 101 | 102 | func (s S) String() string { 103 | return s.Value 104 | } 105 | 106 | func TestJoin_StringerJoin(t *testing.T) { 107 | testCases := []struct { 108 | Arr []interface{ String() string } 109 | Sep string 110 | Expect string 111 | }{ 112 | {[]interface{ String() string }{}, ", ", ""}, 113 | {[]interface{ String() string }{S{"foo"}}, ", ", "foo"}, 114 | {[]interface{ String() string }{S{"foo"}, S{"bar"}, S{"baz"}}, ", ", "foo, bar, baz"}, 115 | } 116 | 117 | for idx, tt := range testCases { 118 | t.Run(fmt.Sprintf("test case #%d", idx+1), func(t *testing.T) { 119 | is := assert.New(t) 120 | 121 | actual := StringerJoin(tt.Arr, tt.Sep) 122 | is.Equal(tt.Expect, actual) 123 | }) 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /lazy_builder.go: -------------------------------------------------------------------------------- 1 | package funk 2 | 3 | import "reflect" 4 | 5 | type lazyBuilder struct { 6 | exec func() interface{} 7 | } 8 | 9 | func (b *lazyBuilder) Chunk(size int) Builder { 10 | return &lazyBuilder{func() interface{} { return Chunk(b.exec(), size) }} 11 | } 12 | func (b *lazyBuilder) Compact() Builder { 13 | return &lazyBuilder{func() interface{} { return Compact(b.exec()) }} 14 | } 15 | func (b *lazyBuilder) Drop(n int) Builder { 16 | return &lazyBuilder{func() interface{} { return Drop(b.exec(), n) }} 17 | } 18 | func (b *lazyBuilder) Filter(predicate interface{}) Builder { 19 | return &lazyBuilder{func() interface{} { return Filter(b.exec(), predicate) }} 20 | } 21 | func (b *lazyBuilder) Flatten() Builder { 22 | return &lazyBuilder{func() interface{} { return Flatten(b.exec()) }} 23 | } 24 | func (b *lazyBuilder) FlattenDeep() Builder { 25 | return &lazyBuilder{func() interface{} { return FlattenDeep(b.exec()) }} 26 | } 27 | func (b *lazyBuilder) Initial() Builder { 28 | return &lazyBuilder{func() interface{} { return Initial(b.exec()) }} 29 | } 30 | func (b *lazyBuilder) Intersect(y interface{}) Builder { 31 | return &lazyBuilder{func() interface{} { return Intersect(b.exec(), y) }} 32 | } 33 | func (b *lazyBuilder) Join(rarr interface{}, fnc JoinFnc) Builder { 34 | return &lazyBuilder{func() interface{} { return Join(b.exec(), rarr, fnc) }} 35 | } 36 | func (b *lazyBuilder) Map(mapFunc interface{}) Builder { 37 | return &lazyBuilder{func() interface{} { return Map(b.exec(), mapFunc) }} 38 | } 39 | func (b *lazyBuilder) FlatMap(mapFunc interface{}) Builder { 40 | return &lazyBuilder{func() interface{} { return FlatMap(b.exec(), mapFunc) }} 41 | } 42 | func (b *lazyBuilder) Reverse() Builder { 43 | return &lazyBuilder{func() interface{} { return Reverse(b.exec()) }} 44 | } 45 | func (b *lazyBuilder) Shuffle() Builder { 46 | return &lazyBuilder{func() interface{} { return Shuffle(b.exec()) }} 47 | } 48 | func (b *lazyBuilder) Tail() Builder { 49 | return &lazyBuilder{func() interface{} { return Tail(b.exec()) }} 50 | } 51 | func (b *lazyBuilder) Uniq() Builder { 52 | return &lazyBuilder{func() interface{} { return Uniq(b.exec()) }} 53 | } 54 | func (b *lazyBuilder) Without(values ...interface{}) Builder { 55 | return &lazyBuilder{func() interface{} { return Without(b.exec(), values...) }} 56 | } 57 | 58 | func (b *lazyBuilder) All() bool { 59 | return (&chainBuilder{b.exec()}).All() 60 | } 61 | func (b *lazyBuilder) Any() bool { 62 | return (&chainBuilder{b.exec()}).Any() 63 | } 64 | func (b *lazyBuilder) Contains(elem interface{}) bool { 65 | return Contains(b.exec(), elem) 66 | } 67 | func (b *lazyBuilder) Every(elements ...interface{}) bool { 68 | return Every(b.exec(), elements...) 69 | } 70 | func (b *lazyBuilder) Find(predicate interface{}) interface{} { 71 | return Find(b.exec(), predicate) 72 | } 73 | func (b *lazyBuilder) ForEach(predicate interface{}) { 74 | ForEach(b.exec(), predicate) 75 | } 76 | func (b *lazyBuilder) ForEachRight(predicate interface{}) { 77 | ForEachRight(b.exec(), predicate) 78 | } 79 | func (b *lazyBuilder) Head() interface{} { 80 | return Head(b.exec()) 81 | } 82 | func (b *lazyBuilder) Keys() interface{} { 83 | return Keys(b.exec()) 84 | } 85 | func (b *lazyBuilder) IndexOf(elem interface{}) int { 86 | return IndexOf(b.exec(), elem) 87 | } 88 | func (b *lazyBuilder) IsEmpty() bool { 89 | return IsEmpty(b.exec()) 90 | } 91 | func (b *lazyBuilder) Last() interface{} { 92 | return Last(b.exec()) 93 | } 94 | func (b *lazyBuilder) LastIndexOf(elem interface{}) int { 95 | return LastIndexOf(b.exec(), elem) 96 | } 97 | func (b *lazyBuilder) NotEmpty() bool { 98 | return NotEmpty(b.exec()) 99 | } 100 | func (b *lazyBuilder) Product() float64 { 101 | return Product(b.exec()) 102 | } 103 | func (b *lazyBuilder) Reduce(reduceFunc, acc interface{}) interface{} { 104 | return Reduce(b.exec(), reduceFunc, acc) 105 | } 106 | func (b *lazyBuilder) Sum() float64 { 107 | return Sum(b.exec()) 108 | } 109 | func (b *lazyBuilder) Type() reflect.Type { 110 | return reflect.TypeOf(b.exec()) 111 | } 112 | func (b *lazyBuilder) Value() interface{} { 113 | return b.exec() 114 | } 115 | func (b *lazyBuilder) Values() interface{} { 116 | return Values(b.exec()) 117 | } 118 | -------------------------------------------------------------------------------- /map.go: -------------------------------------------------------------------------------- 1 | package funk 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | // Keys creates an array of the own enumerable map keys or struct field names. 9 | func Keys(out interface{}) interface{} { 10 | value := redirectValue(reflect.ValueOf(out)) 11 | valueType := value.Type() 12 | 13 | if value.Kind() == reflect.Map { 14 | keys := value.MapKeys() 15 | 16 | length := len(keys) 17 | 18 | resultSlice := reflect.MakeSlice(reflect.SliceOf(valueType.Key()), length, length) 19 | 20 | for i, key := range keys { 21 | resultSlice.Index(i).Set(key) 22 | } 23 | 24 | return resultSlice.Interface() 25 | } 26 | 27 | if value.Kind() == reflect.Struct { 28 | length := value.NumField() 29 | 30 | resultSlice := make([]string, length) 31 | 32 | for i := 0; i < length; i++ { 33 | resultSlice[i] = valueType.Field(i).Name 34 | } 35 | 36 | return resultSlice 37 | } 38 | 39 | panic(fmt.Sprintf("Type %s is not supported by Keys", valueType.String())) 40 | } 41 | 42 | // Values creates an array of the own enumerable map values or struct field values. 43 | func Values(out interface{}) interface{} { 44 | value := redirectValue(reflect.ValueOf(out)) 45 | valueType := value.Type() 46 | 47 | if value.Kind() == reflect.Map { 48 | keys := value.MapKeys() 49 | 50 | length := len(keys) 51 | 52 | resultSlice := reflect.MakeSlice(reflect.SliceOf(valueType.Elem()), length, length) 53 | 54 | for i, key := range keys { 55 | resultSlice.Index(i).Set(value.MapIndex(key)) 56 | } 57 | 58 | return resultSlice.Interface() 59 | } 60 | 61 | if value.Kind() == reflect.Struct { 62 | length := value.NumField() 63 | 64 | resultSlice := make([]interface{}, length) 65 | 66 | for i := 0; i < length; i++ { 67 | resultSlice[i] = value.Field(i).Interface() 68 | } 69 | 70 | return resultSlice 71 | } 72 | 73 | panic(fmt.Sprintf("Type %s is not supported by Keys", valueType.String())) 74 | } 75 | -------------------------------------------------------------------------------- /map_test.go: -------------------------------------------------------------------------------- 1 | package funk 2 | 3 | import ( 4 | "sort" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestKeys(t *testing.T) { 11 | is := assert.New(t) 12 | 13 | results := Keys(map[string]int{"one": 1, "two": 2}).([]string) 14 | sort.Strings(results) 15 | 16 | is.Equal(results, []string{"one", "two"}) 17 | 18 | fields := Keys(foo).([]string) 19 | 20 | sort.Strings(fields) 21 | 22 | is.Equal(fields, []string{"Age", "Bar", "BarInterface", "BarPointer", "Bars", "EmptyValue", "FirstName", "GeneralInterface", "ID", "LastName", "ZeroBoolValue", "ZeroIntPtrValue", "ZeroIntValue"}) 23 | } 24 | 25 | func TestValues(t *testing.T) { 26 | is := assert.New(t) 27 | 28 | results := Values(map[string]int{"one": 1, "two": 2}).([]int) 29 | sort.Ints(results) 30 | 31 | is.Equal(results, []int{1, 2}) 32 | 33 | values := Values(foo).([]interface{}) 34 | 35 | is.Len(values, 13) 36 | } 37 | -------------------------------------------------------------------------------- /max.go: -------------------------------------------------------------------------------- 1 | package funk 2 | 3 | import "strings" 4 | 5 | // MaxInt validates the input, compares the elements and returns the maximum element in an array/slice. 6 | // It accepts []int 7 | // It returns int 8 | func MaxInt(i []int) int { 9 | if len(i) == 0 { 10 | panic("arg is an empty array/slice") 11 | } 12 | var max int 13 | for idx := 0; idx < len(i); idx++ { 14 | item := i[idx] 15 | if idx == 0 { 16 | max = item 17 | continue 18 | } 19 | if item > max { 20 | max = item 21 | } 22 | } 23 | return max 24 | } 25 | 26 | // MaxInt8 validates the input, compares the elements and returns the maximum element in an array/slice. 27 | // It accepts []int8 28 | // It returns int8 29 | func MaxInt8(i []int8) int8 { 30 | if len(i) == 0 { 31 | panic("arg is an empty array/slice") 32 | } 33 | var max int8 34 | for idx := 0; idx < len(i); idx++ { 35 | item := i[idx] 36 | if idx == 0 { 37 | max = item 38 | continue 39 | } 40 | if item > max { 41 | max = item 42 | } 43 | } 44 | return max 45 | } 46 | 47 | // MaxInt16 validates the input, compares the elements and returns the maximum element in an array/slice. 48 | // It accepts []int16 49 | // It returns int16 50 | func MaxInt16(i []int16) int16 { 51 | if len(i) == 0 { 52 | panic("arg is an empty array/slice") 53 | } 54 | var max int16 55 | for idx := 0; idx < len(i); idx++ { 56 | item := i[idx] 57 | if idx == 0 { 58 | max = item 59 | continue 60 | } 61 | if item > max { 62 | max = item 63 | } 64 | } 65 | return max 66 | } 67 | 68 | // MaxInt32 validates the input, compares the elements and returns the maximum element in an array/slice. 69 | // It accepts []int32 70 | // It returns int32 71 | func MaxInt32(i []int32) int32 { 72 | if len(i) == 0 { 73 | panic("arg is an empty array/slice") 74 | } 75 | var max int32 76 | for idx := 0; idx < len(i); idx++ { 77 | item := i[idx] 78 | if idx == 0 { 79 | max = item 80 | continue 81 | } 82 | if item > max { 83 | max = item 84 | } 85 | } 86 | return max 87 | } 88 | 89 | // MaxInt64 validates the input, compares the elements and returns the maximum element in an array/slice. 90 | // It accepts []int64 91 | // It returns int64 92 | func MaxInt64(i []int64) int64 { 93 | if len(i) == 0 { 94 | panic("arg is an empty array/slice") 95 | } 96 | var max int64 97 | for idx := 0; idx < len(i); idx++ { 98 | item := i[idx] 99 | if idx == 0 { 100 | max = item 101 | continue 102 | } 103 | if item > max { 104 | max = item 105 | } 106 | } 107 | return max 108 | } 109 | 110 | // MaxFloat32 validates the input, compares the elements and returns the maximum element in an array/slice. 111 | // It accepts []float32 112 | // It returns float32 113 | func MaxFloat32(i []float32) float32 { 114 | if len(i) == 0 { 115 | panic("arg is an empty array/slice") 116 | } 117 | var max float32 118 | for idx := 0; idx < len(i); idx++ { 119 | item := i[idx] 120 | if idx == 0 { 121 | max = item 122 | continue 123 | } 124 | if item > max { 125 | max = item 126 | } 127 | } 128 | return max 129 | } 130 | 131 | // MaxFloat64 validates the input, compares the elements and returns the maximum element in an array/slice. 132 | // It accepts []float64 133 | // It returns float64 134 | func MaxFloat64(i []float64) float64 { 135 | if len(i) == 0 { 136 | panic("arg is an empty array/slice") 137 | } 138 | var max float64 139 | for idx := 0; idx < len(i); idx++ { 140 | item := i[idx] 141 | if idx == 0 { 142 | max = item 143 | continue 144 | } 145 | if item > max { 146 | max = item 147 | } 148 | } 149 | return max 150 | } 151 | 152 | // MaxString validates the input, compares the elements and returns the maximum element in an array/slice. 153 | // It accepts []string 154 | // It returns string 155 | func MaxString(i []string) string { 156 | if len(i) == 0 { 157 | panic("arg is an empty array/slice") 158 | } 159 | var max string 160 | for idx := 0; idx < len(i); idx++ { 161 | item := i[idx] 162 | if idx == 0 { 163 | max = item 164 | continue 165 | } 166 | max = compareStringsMax(max, item) 167 | } 168 | return max 169 | } 170 | 171 | // compareStrings uses the strings.Compare method to compare two strings, and returns the greater one. 172 | func compareStringsMax(max, current string) string { 173 | r := strings.Compare(strings.ToLower(max), strings.ToLower(current)) 174 | if r > 0 { 175 | return max 176 | } 177 | return current 178 | } 179 | -------------------------------------------------------------------------------- /max_test.go: -------------------------------------------------------------------------------- 1 | package funk 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestMaxWithArrayNumericInput(t *testing.T) { 10 | //Test Data 11 | d1 := []int{8, 3, 4, 44, 0} 12 | n1 := []int{} 13 | d2 := []int8{3, 3, 5, 9, 1} 14 | n2 := []int8{} 15 | d3 := []int16{4, 5, 4, 33, 2} 16 | n3 := []int16{} 17 | d4 := []int32{5, 3, 21, 15, 3} 18 | n4 := []int32{} 19 | d5 := []int64{9, 3, 9, 1, 2} 20 | n5 := []int64{} 21 | //Calls 22 | r1 := MaxInt(d1) 23 | r2 := MaxInt8(d2) 24 | r3 := MaxInt16(d3) 25 | r4 := MaxInt32(d4) 26 | r5 := MaxInt64(d5) 27 | // Assertions 28 | assert.Equal(t, int(44), r1, "It should return the max value in array") 29 | assert.Panics(t, func() { MaxInt(n1) }, "It should panic") 30 | assert.Equal(t, int8(9), r2, "It should return the max value in array") 31 | assert.Panics(t, func() { MaxInt8(n2) }, "It should panic") 32 | assert.Equal(t, int16(33), r3, "It should return the max value in array") 33 | assert.Panics(t, func() { MaxInt16(n3) }, "It should panic") 34 | assert.Equal(t, int32(21), r4, "It should return the max value in array") 35 | assert.Panics(t, func() { MaxInt32(n4) }, "It should panic") 36 | assert.Equal(t, int64(9), r5, "It should return the max value in array") 37 | assert.Panics(t, func() { MaxInt64(n5) }, "It should panic") 38 | 39 | } 40 | 41 | func TestMaxWithArrayFloatInput(t *testing.T) { 42 | //Test Data 43 | d1 := []float64{2, 38.3, 4, 4.4, 4} 44 | n1 := []float64{} 45 | d2 := []float32{2.9, 1.3, 4.23, 4.4, 7.7} 46 | n2 := []float32{} 47 | //Calls 48 | r1 := MaxFloat64(d1) 49 | r2 := MaxFloat32(d2) 50 | // Assertions 51 | assert.Equal(t, float64(38.3), r1, "It should return the max value in array") 52 | assert.Panics(t, func() { MaxFloat64(n1) }, "It should panic") 53 | assert.Equal(t, float32(7.7), r2, "It should return the max value in array") 54 | assert.Panics(t, func() { MaxFloat32(n2) }, "It should panic") 55 | } 56 | 57 | func TestMaxWithArrayInputWithStrings(t *testing.T) { 58 | //Test Data 59 | d1 := []string{"abc", "abd", "cbd"} 60 | d2 := []string{"abc", "abd", "abe"} 61 | d3 := []string{"abc", "foo", " "} 62 | d4 := []string{"abc", "abc", "aaa"} 63 | n1 := []string{} 64 | //Calls 65 | r1 := MaxString(d1) 66 | r2 := MaxString(d2) 67 | r3 := MaxString(d3) 68 | r4 := MaxString(d4) 69 | // Assertions 70 | assert.Equal(t, "cbd", r1, "It should print cbd because its first char is max in the list") 71 | assert.Equal(t, "abe", r2, "It should print abe because its first different char is max in the list") 72 | assert.Equal(t, "foo", r3, "It should print foo because its first different char is max in the list") 73 | assert.Equal(t, "abc", r4, "It should print abc because its first different char is max in the list") 74 | assert.Panics(t, func() { MaxString(n1) }, "It should panic") 75 | } 76 | -------------------------------------------------------------------------------- /min.go: -------------------------------------------------------------------------------- 1 | package funk 2 | 3 | import "strings" 4 | 5 | // MinInt validates the input, compares the elements and returns the minimum element in an array/slice. 6 | // It accepts []int 7 | // It returns int 8 | func MinInt(i []int) int { 9 | if len(i) == 0 { 10 | panic("arg is an empty array/slice") 11 | } 12 | var min int 13 | for idx := 0; idx < len(i); idx++ { 14 | item := i[idx] 15 | if idx == 0 { 16 | min = item 17 | continue 18 | } 19 | if item < min { 20 | min = item 21 | } 22 | } 23 | return min 24 | } 25 | 26 | // MinInt8 validates the input, compares the elements and returns the minimum element in an array/slice. 27 | // It accepts []int8 28 | // It returns int8 29 | func MinInt8(i []int8) int8 { 30 | if len(i) == 0 { 31 | panic("arg is an empty array/slice") 32 | } 33 | var min int8 34 | for idx := 0; idx < len(i); idx++ { 35 | item := i[idx] 36 | if idx == 0 { 37 | min = item 38 | continue 39 | } 40 | if item < min { 41 | min = item 42 | } 43 | } 44 | return min 45 | } 46 | 47 | // MinInt16 validates the input, compares the elements and returns the minimum element in an array/slice. 48 | // It accepts []int16 49 | // It returns int16 50 | func MinInt16(i []int16) int16 { 51 | if len(i) == 0 { 52 | panic("arg is an empty array/slice") 53 | } 54 | var min int16 55 | for idx := 0; idx < len(i); idx++ { 56 | item := i[idx] 57 | if idx == 0 { 58 | min = item 59 | continue 60 | } 61 | if item < min { 62 | min = item 63 | } 64 | } 65 | return min 66 | } 67 | 68 | // MinInt32 validates the input, compares the elements and returns the minimum element in an array/slice. 69 | // It accepts []int32 70 | // It returns int32 71 | func MinInt32(i []int32) int32 { 72 | if len(i) == 0 { 73 | panic("arg is an empty array/slice") 74 | } 75 | var min int32 76 | for idx := 0; idx < len(i); idx++ { 77 | item := i[idx] 78 | if idx == 0 { 79 | min = item 80 | continue 81 | } 82 | if item < min { 83 | min = item 84 | } 85 | } 86 | return min 87 | } 88 | 89 | // MinInt64 validates the input, compares the elements and returns the minimum element in an array/slice. 90 | // It accepts []int64 91 | // It returns int64 92 | func MinInt64(i []int64) int64 { 93 | if len(i) == 0 { 94 | panic("arg is an empty array/slice") 95 | } 96 | var min int64 97 | for idx := 0; idx < len(i); idx++ { 98 | item := i[idx] 99 | if idx == 0 { 100 | min = item 101 | continue 102 | } 103 | if item < min { 104 | min = item 105 | } 106 | } 107 | return min 108 | } 109 | 110 | // MinFloat32 validates the input, compares the elements and returns the minimum element in an array/slice. 111 | // It accepts []float32 112 | // It returns float32 113 | func MinFloat32(i []float32) float32 { 114 | if len(i) == 0 { 115 | panic("arg is an empty array/slice") 116 | } 117 | var min float32 118 | for idx := 0; idx < len(i); idx++ { 119 | item := i[idx] 120 | if idx == 0 { 121 | min = item 122 | continue 123 | } 124 | if item < min { 125 | min = item 126 | } 127 | } 128 | return min 129 | } 130 | 131 | // MinFloat64 validates the input, compares the elements and returns the minimum element in an array/slice. 132 | // It accepts []float64 133 | // It returns float64 134 | func MinFloat64(i []float64) float64 { 135 | if len(i) == 0 { 136 | panic("arg is an empty array/slice") 137 | } 138 | var min float64 139 | for idx := 0; idx < len(i); idx++ { 140 | item := i[idx] 141 | if idx == 0 { 142 | min = item 143 | continue 144 | } 145 | if item < min { 146 | min = item 147 | } 148 | } 149 | return min 150 | } 151 | 152 | // MinString validates the input, compares the elements and returns the minimum element in an array/slice. 153 | // It accepts []string 154 | // It returns string 155 | func MinString(i []string) string { 156 | if len(i) == 0 { 157 | panic("arg is an empty array/slice") 158 | } 159 | var min string 160 | for idx := 0; idx < len(i); idx++ { 161 | item := i[idx] 162 | if idx == 0 { 163 | min = item 164 | continue 165 | } 166 | min = compareStringsMin(min, item) 167 | } 168 | return min 169 | } 170 | 171 | func compareStringsMin(min, current string) string { 172 | r := strings.Compare(strings.ToLower(min), strings.ToLower(current)) 173 | if r < 0 { 174 | return min 175 | } 176 | return current 177 | } 178 | -------------------------------------------------------------------------------- /min_test.go: -------------------------------------------------------------------------------- 1 | package funk 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestMinWithArrayNumericInput(t *testing.T) { 10 | //Test Data 11 | d1 := []int{8, 3, 4, 44, 0} 12 | n1 := []int{} 13 | d2 := []int8{3, 3, 5, 9, 1} 14 | n2 := []int8{} 15 | d3 := []int16{4, 5, 4, 33, 2} 16 | n3 := []int16{} 17 | d4 := []int32{5, 3, 21, 15, 3} 18 | n4 := []int32{} 19 | d5 := []int64{9, 3, 9, 1, 2} 20 | n5 := []int64{} 21 | //Calls 22 | r1 := MinInt(d1) 23 | r2 := MinInt8(d2) 24 | r3 := MinInt16(d3) 25 | r4 := MinInt32(d4) 26 | r5 := MinInt64(d5) 27 | // Assertions 28 | assert.Equal(t, int(0), r1, "It should return the min value in array") 29 | assert.Panics(t, func() { MinInt(n1) }, "It should panic") 30 | assert.Equal(t, int8(1), r2, "It should return the min value in array") 31 | assert.Panics(t, func() { MinInt8(n2) }, "It should panic") 32 | assert.Equal(t, int16(2), r3, "It should return the min value in array") 33 | assert.Panics(t, func() { MinInt16(n3) }, "It should panic") 34 | assert.Equal(t, int32(3), r4, "It should return the min value in array") 35 | assert.Panics(t, func() { MinInt32(n4) }, "It should panic") 36 | assert.Equal(t, int64(1), r5, "It should return the min value in array") 37 | assert.Panics(t, func() { MinInt64(n5) }, "It should panic") 38 | 39 | } 40 | 41 | func TestMinWithArrayFloatInput(t *testing.T) { 42 | //Test Data 43 | d1 := []float64{2, 38.3, 4, 4.4, 4} 44 | n1 := []float64{} 45 | d2 := []float32{2.9, 1.3, 4.23, 4.4, 7.7} 46 | n2 := []float32{} 47 | //Calls 48 | r1 := MinFloat64(d1) 49 | r2 := MinFloat32(d2) 50 | // Assertions 51 | assert.Equal(t, float64(2), r1, "It should return the min value in array") 52 | assert.Panics(t, func() { MinFloat64(n1) }, "It should panic") 53 | assert.Equal(t, float32(1.3), r2, "It should return the min value in array") 54 | assert.Panics(t, func() { MinFloat32(n2) }, "It should panic") 55 | } 56 | 57 | func TestMinWithArrayInputWithStrings(t *testing.T) { 58 | //Test Data 59 | d1 := []string{"abc", "abd", "cbd"} 60 | d2 := []string{"abc", "abd", "abe"} 61 | d3 := []string{"abc", "foo", " "} 62 | d4 := []string{"abc", "abc", "aaa"} 63 | n1 := []string{} 64 | //Calls 65 | r1 := MinString(d1) 66 | r2 := MinString(d2) 67 | r3 := MinString(d3) 68 | r4 := MinString(d4) 69 | // Assertions 70 | assert.Equal(t, "abc", r1, "It should print cbd because its first char is min in the list") 71 | assert.Equal(t, "abc", r2, "It should print abe because its first different char is min in the list") 72 | assert.Equal(t, " ", r3, "It should print foo because its first different char is min in the list") 73 | assert.Equal(t, "aaa", r4, "It should print abc because its first different char is min in the list") 74 | assert.Panics(t, func() { MinString(n1) }, "It should panic") 75 | } 76 | -------------------------------------------------------------------------------- /operation.go: -------------------------------------------------------------------------------- 1 | package funk 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | func calculate(arr interface{}, name string, operation rune) float64 { 9 | value := redirectValue(reflect.ValueOf(arr)) 10 | valueType := value.Type() 11 | 12 | kind := value.Kind() 13 | 14 | if kind == reflect.Array || kind == reflect.Slice { 15 | length := value.Len() 16 | 17 | if length == 0 { 18 | return 0 19 | } 20 | 21 | result := map[rune]float64{ 22 | '+': 0.0, 23 | '*': 1, 24 | }[operation] 25 | 26 | for i := 0; i < length; i++ { 27 | elem := redirectValue(value.Index(i)).Interface() 28 | 29 | var value float64 30 | switch e := elem.(type) { 31 | case int: 32 | value = float64(e) 33 | case int8: 34 | value = float64(e) 35 | case int16: 36 | value = float64(e) 37 | case int32: 38 | value = float64(e) 39 | case int64: 40 | value = float64(e) 41 | case float32: 42 | value = float64(e) 43 | case float64: 44 | value = e 45 | } 46 | 47 | switch operation { 48 | case '+': 49 | result += value 50 | case '*': 51 | result *= value 52 | } 53 | } 54 | 55 | return result 56 | } 57 | 58 | panic(fmt.Sprintf("Type %s is not supported by %s", valueType.String(), name)) 59 | } 60 | 61 | // Sum computes the sum of the values in array. 62 | func Sum(arr interface{}) float64 { 63 | return calculate(arr, "Sum", '+') 64 | } 65 | 66 | // Product computes the product of the values in array. 67 | func Product(arr interface{}) float64 { 68 | return calculate(arr, "Product", '*') 69 | } 70 | -------------------------------------------------------------------------------- /operation_test.go: -------------------------------------------------------------------------------- 1 | package funk 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestSum(t *testing.T) { 10 | is := assert.New(t) 11 | 12 | is.Equal(Sum([]int{1, 2, 3}), 6.0) 13 | is.Equal(Sum(&[]int{1, 2, 3}), 6.0) 14 | is.Equal(Sum([]interface{}{1, 2, 3, 0.5}), 6.5) 15 | } 16 | 17 | func TestProduct(t *testing.T) { 18 | is := assert.New(t) 19 | 20 | is.Equal(Product([]int{2, 3, 4}), 24.0) 21 | is.Equal(Product(&[]int{2, 3, 4}), 24.0) 22 | is.Equal(Product([]interface{}{2, 3, 4, 0.5}), 12.0) 23 | } 24 | -------------------------------------------------------------------------------- /options.go: -------------------------------------------------------------------------------- 1 | package funk 2 | 3 | type options struct { 4 | allowZero bool 5 | } 6 | 7 | type option func(*options) 8 | 9 | func newOptions(values ...option) *options { 10 | opts := &options{ 11 | allowZero: false, 12 | } 13 | for _, o := range values { 14 | o(opts) 15 | } 16 | return opts 17 | } 18 | 19 | // WithAllowZero allows zero values. 20 | func WithAllowZero() func(*options) { 21 | return func(opts *options) { 22 | opts.allowZero = true 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /permutation.go: -------------------------------------------------------------------------------- 1 | package funk 2 | 3 | import "errors" 4 | 5 | // NextPermutation Implement next permutation, 6 | // which rearranges numbers into the lexicographically next greater permutation of numbers. 7 | func NextPermutation(nums []int) error { 8 | n := len(nums) 9 | if n == 0 { 10 | return errors.New("nums is empty") 11 | } 12 | 13 | i := n - 2 14 | 15 | for i >= 0 && nums[i] >= nums[i+1] { 16 | i-- 17 | } 18 | 19 | if i >= 0 { 20 | j := n - 1 21 | for j >= 0 && nums[i] >= nums[j] { 22 | j-- 23 | } 24 | nums[i], nums[j] = nums[j], nums[i] 25 | } 26 | 27 | ReverseInt(nums[i+1:]) 28 | return nil 29 | } 30 | -------------------------------------------------------------------------------- /permutation_test.go: -------------------------------------------------------------------------------- 1 | package funk 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestNextPermutation(t *testing.T) { 9 | type args struct { 10 | nums []int 11 | } 12 | tests := []struct { 13 | name string 14 | args args 15 | wantErr bool 16 | }{ 17 | { 18 | name: "case1", 19 | args: args{ 20 | nums: []int{1, 2, 3}, 21 | }, 22 | wantErr: false, 23 | }, 24 | } 25 | for _, tt := range tests { 26 | t.Run(tt.name, func(t *testing.T) { 27 | if err := NextPermutation(tt.args.nums); (err != nil) != tt.wantErr { 28 | t.Errorf("NextPermutation() error = %v, wantErr %v", err, tt.wantErr) 29 | } else { 30 | fmt.Println(tt.args.nums) 31 | } 32 | }) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /predicate.go: -------------------------------------------------------------------------------- 1 | package funk 2 | 3 | import ( 4 | "reflect" 5 | ) 6 | 7 | // predicatesImpl contains the common implementation of AnyPredicates and AllPredicates. 8 | func predicatesImpl(value interface{}, wantedAnswer bool, predicates interface{}) bool { 9 | if !IsCollection(predicates) { 10 | panic("Predicates parameter must be an iteratee") 11 | } 12 | 13 | predicatesValue := reflect.ValueOf(predicates) 14 | inputValue := reflect.ValueOf(value) 15 | 16 | for i := 0; i < predicatesValue.Len(); i++ { 17 | funcValue := predicatesValue.Index(i) 18 | if !IsFunction(funcValue.Interface()) { 19 | panic("Got non function as predicate") 20 | } 21 | 22 | funcType := funcValue.Type() 23 | if !IsPredicate(funcValue.Interface()) { 24 | panic("Predicate function must have 1 parameter and must return boolean") 25 | } 26 | 27 | if !inputValue.Type().ConvertibleTo(funcType.In(0)) { 28 | panic("Given value is not compatible with type of parameter for the predicate.") 29 | } 30 | if result := funcValue.Call([]reflect.Value{inputValue}); wantedAnswer == result[0].Bool() { 31 | return wantedAnswer 32 | } 33 | } 34 | 35 | return !wantedAnswer 36 | } 37 | 38 | // AnyPredicates gets a value and a series of predicates, and return true if at least one of the predicates 39 | // is true. 40 | func AnyPredicates(value interface{}, predicates interface{}) bool { 41 | return predicatesImpl(value, true, predicates) 42 | } 43 | 44 | // AllPredicates gets a value and a series of predicates, and return true if all of the predicates are true. 45 | func AllPredicates(value interface{}, predicates interface{}) bool { 46 | return predicatesImpl(value, false, predicates) 47 | } 48 | -------------------------------------------------------------------------------- /predicate_test.go: -------------------------------------------------------------------------------- 1 | package funk 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func TestAllPredicates(t *testing.T) { 11 | type args struct { 12 | value interface{} 13 | predicates interface{} 14 | } 15 | tests := []struct { 16 | name string 17 | args args 18 | want bool 19 | }{ 20 | { 21 | name: "Sanity string predicates", 22 | args: args{ 23 | value: "test", 24 | predicates: []func(string) bool{ 25 | func(v string) bool { return strings.Contains(v, "est") }, 26 | func(v string) bool { return len(v) < 10 }, 27 | func(v string) bool { return len(v) > 2 }, 28 | }, 29 | }, 30 | want: true, 31 | }, 32 | { 33 | name: "Sanity int predicates", 34 | args: args{ 35 | value: 4, 36 | predicates: []func(int) bool{ 37 | func(v int) bool { return v < 5 }, 38 | func(v int) bool { return v > 2 }, 39 | }, 40 | }, 41 | want: true, 42 | }, 43 | { 44 | name: "Failed predicate", 45 | args: args{ 46 | value: "test", 47 | predicates: []func(string) bool{ 48 | func(v string) bool { return strings.Contains(v, "est") }, 49 | func(v string) bool { return len(v) > 10 }, 50 | func(v string) bool { return len(v) > 2 }, 51 | }, 52 | }, 53 | want: false, 54 | }, 55 | } 56 | for _, tt := range tests { 57 | t.Run(tt.name, func(t *testing.T) { 58 | if got := AllPredicates(tt.args.value, tt.args.predicates); got != tt.want { 59 | t.Errorf("AllPredicates() = %v, want %v", got, tt.want) 60 | } 61 | }) 62 | } 63 | } 64 | 65 | func TestAnyPredicates(t *testing.T) { 66 | type args struct { 67 | value interface{} 68 | predicates interface{} 69 | } 70 | tests := []struct { 71 | name string 72 | args args 73 | want bool 74 | }{ 75 | { 76 | name: "Sanity string predicates", 77 | args: args{ 78 | value: "test", 79 | predicates: []func(string) bool{ 80 | func(v string) bool { return strings.Contains(v, "est") }, 81 | func(v string) bool { return len(v) > 10 }, 82 | func(v string) bool { return len(v) < 2 }, 83 | }, 84 | }, 85 | want: true, 86 | }, 87 | { 88 | name: "Sanity int predicates", 89 | args: args{ 90 | value: 4, 91 | predicates: []func(int) bool{ 92 | func(v int) bool { return v > 5 }, 93 | func(v int) bool { return v > 2 }, 94 | }, 95 | }, 96 | want: true, 97 | }, 98 | { 99 | name: "All failed predicate", 100 | args: args{ 101 | value: "test", 102 | predicates: []func(string) bool{ 103 | func(v string) bool { return !strings.Contains(v, "est") }, 104 | func(v string) bool { return len(v) > 10 }, 105 | func(v string) bool { return len(v) < 2 }, 106 | }, 107 | }, 108 | want: false, 109 | }, 110 | } 111 | for _, tt := range tests { 112 | t.Run(tt.name, func(t *testing.T) { 113 | if got := AnyPredicates(tt.args.value, tt.args.predicates); got != tt.want { 114 | t.Errorf("AnyPredicates() = %v, want %v", got, tt.want) 115 | } 116 | }) 117 | } 118 | } 119 | 120 | func TestPredicatesImplPanics(t *testing.T) { 121 | type args struct { 122 | value interface{} 123 | wantedAnswer bool 124 | predicates interface{} 125 | } 126 | tests := []struct { 127 | name string 128 | args args 129 | }{ 130 | { 131 | name: "predicates are not collection", 132 | args: args{ 133 | value: nil, 134 | wantedAnswer: false, 135 | predicates: nil, 136 | }, 137 | }, 138 | { 139 | name: "predicates are collection of strings", 140 | args: args{ 141 | value: nil, 142 | wantedAnswer: false, 143 | predicates: []string{"hey"}, 144 | }, 145 | }, 146 | { 147 | name: "predicate has 2 out parameters", 148 | args: args{ 149 | value: nil, 150 | wantedAnswer: false, 151 | predicates: []func(string) (bool, error){ func(string) (bool, error){return true, nil}}, 152 | }, 153 | }, 154 | { 155 | name: "predicate has out parameter of type string", 156 | args: args{ 157 | value: nil, 158 | wantedAnswer: false, 159 | predicates: []func(string) string{ func(string) string{return ""}}, 160 | }, 161 | }, 162 | { 163 | name: "predicate has 2 in parameters", 164 | args: args{ 165 | value: nil, 166 | wantedAnswer: false, 167 | predicates: []func(string, bool) bool{ func(string, bool) bool{return true}}, 168 | }, 169 | }, 170 | { 171 | name: "predicate has 0 in parameters", 172 | args: args{ 173 | value: nil, 174 | wantedAnswer: false, 175 | predicates: []func() bool{ func() bool{return true}}, 176 | }, 177 | }, 178 | { 179 | name: "value is not convertible to in parameter", 180 | args: args{ 181 | value: 1, 182 | wantedAnswer: false, 183 | predicates: []func(string) bool{ func(string) bool{return true}}, 184 | }, 185 | }, 186 | } 187 | for _, tt := range tests { 188 | t.Run(tt.name, func(t *testing.T) { 189 | require.Panics(t, func() {predicatesImpl(tt.args.value, tt.args.wantedAnswer, tt.args.predicates)}) 190 | }) 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /presence.go: -------------------------------------------------------------------------------- 1 | package funk 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "strings" 7 | ) 8 | 9 | // Filter iterates over elements of collection, returning an array of 10 | // all elements predicate returns truthy for. 11 | func Filter(arr interface{}, predicate interface{}) interface{} { 12 | if !IsIteratee(arr) { 13 | panic("First parameter must be an iteratee") 14 | } 15 | 16 | if !IsFunction(predicate, 1, 1) { 17 | panic("Second argument must be function") 18 | } 19 | 20 | funcValue := reflect.ValueOf(predicate) 21 | 22 | funcType := funcValue.Type() 23 | 24 | if funcType.Out(0).Kind() != reflect.Bool { 25 | panic("Return argument should be a boolean") 26 | } 27 | 28 | arrValue := reflect.ValueOf(arr) 29 | 30 | arrType := arrValue.Type() 31 | 32 | // Get slice type corresponding to array type 33 | resultSliceType := reflect.SliceOf(arrType.Elem()) 34 | 35 | // MakeSlice takes a slice kind type, and makes a slice. 36 | resultSlice := reflect.MakeSlice(resultSliceType, 0, 0) 37 | 38 | for i := 0; i < arrValue.Len(); i++ { 39 | elem := arrValue.Index(i) 40 | 41 | result := funcValue.Call([]reflect.Value{elem})[0].Interface().(bool) 42 | 43 | if result { 44 | resultSlice = reflect.Append(resultSlice, elem) 45 | } 46 | } 47 | 48 | return resultSlice.Interface() 49 | } 50 | 51 | // Find iterates over elements of collection, returning the first 52 | // element predicate returns truthy for. 53 | func Find(arr interface{}, predicate interface{}) interface{} { 54 | _, val := FindKey(arr, predicate) 55 | return val 56 | } 57 | 58 | // Find iterates over elements of collection, returning the first 59 | // element of an array and random of a map which predicate returns truthy for. 60 | func FindKey(arr interface{}, predicate interface{}) (matchKey, matchEle interface{}) { 61 | if !IsIteratee(arr) { 62 | panic("First parameter must be an iteratee") 63 | } 64 | 65 | if !IsFunction(predicate, 1, 1) { 66 | panic("Second argument must be function") 67 | } 68 | 69 | funcValue := reflect.ValueOf(predicate) 70 | 71 | funcType := funcValue.Type() 72 | 73 | if funcType.Out(0).Kind() != reflect.Bool { 74 | panic("Return argument should be a boolean") 75 | } 76 | 77 | arrValue := reflect.ValueOf(arr) 78 | var keyArrs []reflect.Value 79 | 80 | isMap := arrValue.Kind() == reflect.Map 81 | if isMap { 82 | keyArrs = arrValue.MapKeys() 83 | } 84 | for i := 0; i < arrValue.Len(); i++ { 85 | var ( 86 | elem reflect.Value 87 | key reflect.Value 88 | ) 89 | if isMap { 90 | key = keyArrs[i] 91 | elem = arrValue.MapIndex(key) 92 | } else { 93 | key = reflect.ValueOf(i) 94 | elem = arrValue.Index(i) 95 | } 96 | 97 | result := funcValue.Call([]reflect.Value{elem})[0].Interface().(bool) 98 | 99 | if result { 100 | return key.Interface(), elem.Interface() 101 | } 102 | } 103 | 104 | return nil, nil 105 | } 106 | 107 | // IndexOf gets the index at which the first occurrence of value is found in array or return -1 108 | // if the value cannot be found 109 | func IndexOf(in interface{}, elem interface{}) int { 110 | inValue := reflect.ValueOf(in) 111 | 112 | elemValue := reflect.ValueOf(elem) 113 | 114 | inType := inValue.Type() 115 | 116 | if inType.Kind() == reflect.String { 117 | return strings.Index(inValue.String(), elemValue.String()) 118 | } 119 | 120 | if inType.Kind() == reflect.Slice { 121 | equalTo := equal(elem) 122 | for i := 0; i < inValue.Len(); i++ { 123 | if equalTo(reflect.Value{}, inValue.Index(i)) { 124 | return i 125 | } 126 | } 127 | } 128 | 129 | return -1 130 | } 131 | 132 | // LastIndexOf gets the index at which the last occurrence of value is found in array or return -1 133 | // if the value cannot be found 134 | func LastIndexOf(in interface{}, elem interface{}) int { 135 | inValue := reflect.ValueOf(in) 136 | 137 | elemValue := reflect.ValueOf(elem) 138 | 139 | inType := inValue.Type() 140 | 141 | if inType.Kind() == reflect.String { 142 | return strings.LastIndex(inValue.String(), elemValue.String()) 143 | } 144 | 145 | if inType.Kind() == reflect.Slice { 146 | length := inValue.Len() 147 | 148 | equalTo := equal(elem) 149 | for i := length - 1; i >= 0; i-- { 150 | if equalTo(reflect.Value{}, inValue.Index(i)) { 151 | return i 152 | } 153 | } 154 | } 155 | 156 | return -1 157 | } 158 | 159 | // Contains returns true if an element is present in a iteratee. 160 | func Contains(in interface{}, elem interface{}) bool { 161 | inValue := reflect.ValueOf(in) 162 | elemValue := reflect.ValueOf(elem) 163 | inType := inValue.Type() 164 | 165 | switch inType.Kind() { 166 | case reflect.String: 167 | return strings.Contains(inValue.String(), elemValue.String()) 168 | case reflect.Map: 169 | equalTo := equal(elem, true) 170 | for _, key := range inValue.MapKeys() { 171 | if equalTo(key, inValue.MapIndex(key)) { 172 | return true 173 | } 174 | } 175 | case reflect.Slice, reflect.Array: 176 | equalTo := equal(elem) 177 | for i := 0; i < inValue.Len(); i++ { 178 | if equalTo(reflect.Value{}, inValue.Index(i)) { 179 | return true 180 | } 181 | } 182 | default: 183 | panic(fmt.Sprintf("Type %s is not supported by Contains, supported types are String, Map, Slice, Array", inType.String())) 184 | } 185 | 186 | return false 187 | } 188 | 189 | // Every returns true if every element is present in a iteratee. 190 | func Every(in interface{}, elements ...interface{}) bool { 191 | for _, elem := range elements { 192 | if !Contains(in, elem) { 193 | return false 194 | } 195 | } 196 | return true 197 | } 198 | 199 | // Some returns true if atleast one element is present in an iteratee. 200 | func Some(in interface{}, elements ...interface{}) bool { 201 | for _, elem := range elements { 202 | if Contains(in, elem) { 203 | return true 204 | } 205 | } 206 | return false 207 | } 208 | -------------------------------------------------------------------------------- /presence_test.go: -------------------------------------------------------------------------------- 1 | package funk 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | var f = &Foo{ 10 | ID: 1, 11 | FirstName: "Dark", 12 | LastName: "Vador", 13 | Age: 30, 14 | Bar: &Bar{ 15 | Name: "Test", 16 | }, 17 | } 18 | 19 | var b = &Foo{ 20 | ID: 2, 21 | FirstName: "Florent", 22 | LastName: "Messa", 23 | Age: 28, 24 | } 25 | var c = &Foo{ 26 | ID: 3, 27 | FirstName: "Harald", 28 | LastName: "Nordgren", 29 | Age: 27, 30 | } 31 | 32 | var results = []*Foo{f, c} 33 | 34 | type Person struct { 35 | name string 36 | age int 37 | } 38 | 39 | func TestContains(t *testing.T) { 40 | is := assert.New(t) 41 | 42 | is.True(Contains([]string{"foo", "bar"}, "bar")) 43 | is.True(Contains([...]string{"foo", "bar"}, "bar")) 44 | is.Panics(func() { Contains(1, 2) }) 45 | 46 | is.True(Contains(results, f)) 47 | is.False(Contains(results, nil)) 48 | is.False(Contains(results, b)) 49 | 50 | is.True(Contains("florent", "rent")) 51 | is.False(Contains("florent", "gilles")) 52 | 53 | mapping := ToMap(results, "ID") 54 | 55 | is.True(Contains(mapping, 1)) 56 | is.False(Contains(mapping, 2)) 57 | 58 | is.False(Contains(mapping, func(key int, val *Foo) bool { 59 | return key == 4 60 | })) 61 | is.True(Contains(mapping, func(key int, val *Foo) bool { 62 | return key == 1 63 | })) 64 | 65 | is.False(Contains(mapping, func(_ int, val *Foo) bool { 66 | return val.FirstName == "NotPresent" 67 | })) 68 | is.True(Contains(mapping, func(_ int, val *Foo) bool { 69 | return val.FirstName == "Harald" 70 | })) 71 | } 72 | 73 | func TestEvery(t *testing.T) { 74 | is := assert.New(t) 75 | 76 | is.True(Every([]string{"foo", "bar", "baz"}, "bar", "foo")) 77 | 78 | is.True(Every(results, f, c)) 79 | is.False(Every(results, nil)) 80 | is.False(Every(results, f, b)) 81 | 82 | is.True(Every("florent", "rent", "flo")) 83 | is.False(Every("florent", "rent", "gilles")) 84 | 85 | mapping := ToMap(results, "ID") 86 | 87 | is.True(Every(mapping, 1, 3)) 88 | is.False(Every(mapping, 2, 3)) 89 | } 90 | 91 | func TestSome(t *testing.T) { 92 | is := assert.New(t) 93 | 94 | is.True(Some([]string{"foo", "bar", "baz"}, "foo")) 95 | is.True(Some([]string{"foo", "bar", "baz"}, "foo", "qux")) 96 | 97 | is.True(Some(results, f)) 98 | is.False(Some(results, b)) 99 | is.False(Some(results, nil)) 100 | is.True(Some(results, f, b)) 101 | 102 | is.True(Some("zeeshan", "zee", "tam")) 103 | is.False(Some("zeeshan", "zi", "tam")) 104 | 105 | persons := []Person{ 106 | { 107 | name: "Zeeshan", 108 | age: 23, 109 | }, 110 | { 111 | name: "Bob", 112 | age: 26, 113 | }, 114 | } 115 | 116 | person := Person{"Zeeshan", 23} 117 | person2 := Person{"Alice", 23} 118 | person3 := Person{"John", 26} 119 | 120 | is.True(Some(persons, person, person2)) 121 | is.False(Some(persons, person2, person3)) 122 | 123 | mapping := ToMap(results, "ID") 124 | 125 | is.True(Some(mapping, 1, 2)) 126 | is.True(Some(mapping, 4, 1)) 127 | is.False(Some(mapping, 4, 5)) 128 | } 129 | 130 | func TestIndexOf(t *testing.T) { 131 | is := assert.New(t) 132 | 133 | is.Equal(IndexOf([]string{"foo", "bar"}, "bar"), 1) 134 | is.Equal(IndexOf([]string{"foo", "bar"}, func(value string) bool { 135 | return value == "bar" 136 | }), 1) 137 | 138 | is.Equal(IndexOf(results, f), 0) 139 | is.Equal(IndexOf(results, b), -1) 140 | } 141 | 142 | func TestLastIndexOf(t *testing.T) { 143 | is := assert.New(t) 144 | 145 | is.Equal(LastIndexOf([]string{"foo", "bar", "bar"}, "bar"), 2) 146 | is.Equal(LastIndexOf([]string{"foo", "bar", "bar"}, func(value string) bool { 147 | return value == "bar" 148 | }), 2) 149 | is.Equal(LastIndexOf([]int{1, 2, 2, 3}, 2), 2) 150 | is.Equal(LastIndexOf([]int{1, 2, 2, 3}, 4), -1) 151 | } 152 | 153 | func TestFilter(t *testing.T) { 154 | is := assert.New(t) 155 | 156 | r := Filter([]int{1, 2, 3, 4}, func(x int) bool { 157 | return x%2 == 0 158 | }) 159 | 160 | is.Equal(r, []int{2, 4}) 161 | } 162 | 163 | func TestFind(t *testing.T) { 164 | is := assert.New(t) 165 | 166 | r := Find([]int{1, 2, 3, 4}, func(x int) bool { 167 | return x%2 == 0 168 | }) 169 | 170 | is.Equal(r, 2) 171 | 172 | } 173 | func TestFindKey(t *testing.T) { 174 | is := assert.New(t) 175 | 176 | k, r := FindKey(map[string]int{"a": 1, "b": 2, "c": 3, "d": 4}, func(x int) bool { 177 | return x == 2 178 | }) 179 | 180 | is.Equal(r, 2) 181 | is.Equal(k, "b") 182 | 183 | k1, r1 := FindKey([]int{1, 2, 3, 4}, func(x int) bool { 184 | return x%2 == 0 185 | }) 186 | is.Equal(r1, 2) 187 | is.Equal(k1, 1) 188 | } 189 | -------------------------------------------------------------------------------- /reduce.go: -------------------------------------------------------------------------------- 1 | package funk 2 | 3 | import ( 4 | "reflect" 5 | ) 6 | 7 | // Reduce takes a collection and reduces it to a single value using a reduction 8 | // function (or a valid symbol) and an accumulator value. 9 | func Reduce(arr, reduceFunc, acc interface{}) interface{} { 10 | arrValue := redirectValue(reflect.ValueOf(arr)) 11 | 12 | if !IsIteratee(arrValue.Interface()) { 13 | panic("First parameter must be an iteratee") 14 | } 15 | 16 | returnType := reflect.TypeOf(Reduce).Out(0) 17 | 18 | isFunc := IsFunction(reduceFunc, 2, 1) 19 | isRune := reflect.TypeOf(reduceFunc).Kind() == reflect.Int32 20 | 21 | if !(isFunc || isRune) { 22 | panic("Second argument must be a valid function or rune") 23 | } 24 | 25 | accValue := reflect.ValueOf(acc) 26 | sliceElemType := sliceElem(arrValue.Type()) 27 | 28 | if isRune { 29 | if arrValue.Kind() == reflect.Slice && sliceElemType.Kind() == reflect.Interface { 30 | accValue = accValue.Convert(returnType) 31 | } else { 32 | accValue = accValue.Convert(sliceElemType) 33 | } 34 | } else { 35 | accValue = accValue.Convert(reflect.TypeOf(reduceFunc).In(0)) 36 | } 37 | 38 | accType := accValue.Type() 39 | 40 | // Generate reduce function if was passed as rune 41 | if isRune { 42 | reduceSign := reduceFunc.(int32) 43 | 44 | if ok := map[rune]bool{'+': true, '*': true}[reduceSign]; !ok { 45 | panic("Invalid reduce sign, allowed: '+' and '*'") 46 | } 47 | 48 | in := []reflect.Type{accType, sliceElemType} 49 | out := []reflect.Type{accType} 50 | funcType := reflect.FuncOf(in, out, false) 51 | 52 | reduceFunc = reflect.MakeFunc(funcType, func(args []reflect.Value) []reflect.Value { 53 | acc := args[0].Interface() 54 | elem := args[1].Interface() 55 | 56 | var result float64 57 | params := []interface{}{acc, elem} 58 | switch reduceSign { 59 | case '+': 60 | result = Sum(params) 61 | case '*': 62 | result = Product(params) 63 | } 64 | 65 | return []reflect.Value{reflect.ValueOf(result).Convert(accType)} 66 | }).Interface() 67 | } 68 | 69 | funcValue := reflect.ValueOf(reduceFunc) 70 | funcType := funcValue.Type() 71 | 72 | for i := 0; i < arrValue.Len(); i++ { 73 | if accType.ConvertibleTo(funcType.In(0)) { 74 | accValue = accValue.Convert(funcType.In(0)) 75 | } 76 | 77 | arrElementValue := arrValue.Index(i) 78 | if sliceElemType.ConvertibleTo(funcType.In(1)) { 79 | arrElementValue = arrElementValue.Convert(funcType.In(1)) 80 | } 81 | 82 | result := funcValue.Call([]reflect.Value{accValue, arrElementValue}) 83 | accValue = result[0] 84 | } 85 | 86 | return accValue.Convert(returnType).Interface() 87 | } 88 | -------------------------------------------------------------------------------- /reduce_test.go: -------------------------------------------------------------------------------- 1 | package funk 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestReduce(t *testing.T) { 11 | testCases := []struct { 12 | Arr interface{} 13 | Func interface{} 14 | Acc interface{} 15 | Result interface{} 16 | }{ 17 | { 18 | []int{1, 2, 3, 4}, 19 | func(acc, elem float64) float64 { return acc + elem }, 20 | 0, 21 | float64(10), 22 | }, 23 | { 24 | &[]int16{1, 2, 3, 4}, 25 | '+', 26 | 5, 27 | int16(15), 28 | }, 29 | { 30 | []float64{1.1, 2.2, 3.3}, 31 | '+', 32 | 0, 33 | float64(6.6), 34 | }, 35 | { 36 | &[]int{1, 2, 3, 5}, 37 | func(acc int8, elem int16) int32 { return int32(acc) * int32(elem) }, 38 | 1, 39 | int32(30), 40 | }, 41 | { 42 | []interface{}{1, 2, 3.3, 4}, 43 | '*', 44 | 1, 45 | float64(26.4), 46 | }, 47 | { 48 | []string{"1", "2", "3", "4"}, 49 | func(acc string, elem string) string { return acc + elem }, 50 | "", 51 | "1234", 52 | }, 53 | } 54 | 55 | for idx, test := range testCases { 56 | t.Run(fmt.Sprintf("test case #%d", idx+1), func(t *testing.T) { 57 | is := assert.New(t) 58 | result := Reduce(test.Arr, test.Func, test.Acc) 59 | if !is.Equal(result, test.Result) { 60 | t.Errorf("%#v doesn't eqal to %#v", result, test.Result) 61 | } 62 | }) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /retrieve.go: -------------------------------------------------------------------------------- 1 | package funk 2 | 3 | import ( 4 | "reflect" 5 | "strings" 6 | ) 7 | 8 | // Get retrieves the value from given path, retriever can be modified with available RetrieverOptions 9 | func Get(out interface{}, path string, opts ...option) interface{} { 10 | options := newOptions(opts...) 11 | 12 | result := get(reflect.ValueOf(out), path, opts...) 13 | // valid kind and we can return a result.Interface() without panic 14 | if result.Kind() != reflect.Invalid && result.CanInterface() { 15 | // if we don't allow zero and the result is a zero value return nil 16 | if !options.allowZero && result.IsZero() { 17 | return nil 18 | } 19 | // if the result kind is a pointer and its nil return nil 20 | if result.Kind() == reflect.Ptr && result.IsNil() { 21 | return nil 22 | } 23 | // return the result interface (i.e the zero value of it) 24 | return result.Interface() 25 | } 26 | 27 | return nil 28 | } 29 | 30 | // GetOrElse retrieves the value of the pointer or default. 31 | func GetOrElse(v interface{}, def interface{}) interface{} { 32 | val := reflect.ValueOf(v) 33 | if v == nil || (val.Kind() == reflect.Ptr && val.IsNil()) { 34 | return def 35 | } else if val.Kind() != reflect.Ptr { 36 | return v 37 | } 38 | return val.Elem().Interface() 39 | } 40 | 41 | func get(value reflect.Value, path string, opts ...option) reflect.Value { 42 | options := newOptions(opts...) 43 | 44 | if value.Kind() == reflect.Slice || value.Kind() == reflect.Array { 45 | var resultSlice reflect.Value 46 | 47 | length := value.Len() 48 | 49 | if length == 0 { 50 | zeroElement := reflect.Zero(value.Type().Elem()) 51 | pathValue := get(zeroElement, path) 52 | value = reflect.MakeSlice(reflect.SliceOf(pathValue.Type()), 0, 0) 53 | 54 | return value 55 | } 56 | 57 | for i := 0; i < length; i++ { 58 | item := value.Index(i) 59 | 60 | resultValue := get(item, path) 61 | 62 | if resultValue.Kind() == reflect.Invalid || (resultValue.IsZero() && !options.allowZero) { 63 | continue 64 | } 65 | 66 | resultType := resultValue.Type() 67 | 68 | if resultSlice.Kind() == reflect.Invalid { 69 | resultType := reflect.SliceOf(resultType) 70 | 71 | resultSlice = reflect.MakeSlice(resultType, 0, 0) 72 | } 73 | 74 | resultSlice = reflect.Append(resultSlice, resultValue) 75 | } 76 | 77 | // if the result is a slice of a slice, we need to flatten it 78 | if resultSlice.Kind() != reflect.Invalid && resultSlice.Type().Elem().Kind() == reflect.Slice { 79 | return flattenDeep(resultSlice) 80 | } 81 | 82 | return resultSlice 83 | } 84 | 85 | quoted := false 86 | parts := strings.FieldsFunc(path, func(r rune) bool { 87 | if r == '"' { 88 | quoted = !quoted 89 | } 90 | return !quoted && r == '.' 91 | }) 92 | 93 | for i, part := range parts { 94 | parts[i] = strings.Trim(part, "\"") 95 | } 96 | 97 | for _, part := range parts { 98 | value = redirectValue(value) 99 | kind := value.Kind() 100 | 101 | switch kind { 102 | case reflect.Invalid: 103 | continue 104 | case reflect.Struct: 105 | if isNilIndirection(value, part) { 106 | return reflect.ValueOf(nil) 107 | } 108 | value = value.FieldByName(part) 109 | case reflect.Map: 110 | value = value.MapIndex(reflect.ValueOf(part)) 111 | case reflect.Slice, reflect.Array: 112 | value = get(value, part) 113 | default: 114 | return reflect.ValueOf(nil) 115 | } 116 | } 117 | 118 | return value 119 | } 120 | 121 | func isNilIndirection(v reflect.Value, name string) bool { 122 | vType := v.Type() 123 | for i := 0; i < vType.NumField(); i++ { 124 | field := vType.Field(i) 125 | if !isEmbeddedStructPointerField(field) { 126 | return false 127 | } 128 | 129 | fieldType := field.Type.Elem() 130 | 131 | _, found := fieldType.FieldByName(name) 132 | if found { 133 | return v.Field(i).IsNil() 134 | } 135 | } 136 | 137 | return false 138 | } 139 | 140 | func isEmbeddedStructPointerField(field reflect.StructField) bool { 141 | if !field.Anonymous { 142 | return false 143 | } 144 | 145 | return field.Type.Kind() == reflect.Ptr && field.Type.Elem().Kind() == reflect.Struct 146 | } 147 | -------------------------------------------------------------------------------- /retrieve_test.go: -------------------------------------------------------------------------------- 1 | package funk 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestGetSlice(t *testing.T) { 10 | is := assert.New(t) 11 | 12 | is.Equal(Get(SliceOf(foo), "ID"), []int{1}) 13 | is.Equal(Get(SliceOf(foo), "Bar.Name"), []string{"Test"}) 14 | is.Equal(Get(SliceOf(foo), "Bar"), []*Bar{bar}) 15 | is.Equal(Get(([]Foo)(nil), "Bar.Name"), []string{}) 16 | is.Equal(Get([]Foo{}, "Bar.Name"), []string{}) 17 | is.Equal(Get([]*Foo{}, "Bar.Name"), []string{}) 18 | } 19 | 20 | func TestGetSliceMultiLevel(t *testing.T) { 21 | is := assert.New(t) 22 | 23 | is.Equal(Get(foo, "Bar.Bars.Bar.Name"), []string{"Level2-1", "Level2-2"}) 24 | is.Equal(Get(SliceOf(foo), "Bar.Bars.Bar.Name"), []string{"Level2-1", "Level2-2"}) 25 | } 26 | 27 | func TestGetNull(t *testing.T) { 28 | is := assert.New(t) 29 | 30 | is.Equal(Get(foo, "EmptyValue.Int64"), int64(10)) 31 | is.Equal(Get(foo, "ZeroValue"), nil) 32 | is.Equal(false, Get(foo, "ZeroBoolValue", WithAllowZero())) 33 | is.Equal(nil, Get(fooUnexported, "unexported", WithAllowZero())) 34 | is.Equal(nil, Get(fooUnexported, "unexported", WithAllowZero())) 35 | is.Equal(Get(foo, "ZeroIntValue", WithAllowZero()), 0) 36 | is.Equal(Get(foo, "ZeroIntPtrValue", WithAllowZero()), nil) 37 | is.Equal(Get(foo, "EmptyValue.Int64", WithAllowZero()), int64(10)) 38 | is.Equal(Get(SliceOf(foo), "EmptyValue.Int64"), []int64{10}) 39 | } 40 | 41 | func TestGetNil(t *testing.T) { 42 | is := assert.New(t) 43 | is.Equal(Get(foo2, "Bar.Name"), nil) 44 | is.Equal(Get(foo2, "Bar.Name", WithAllowZero()), "") 45 | is.Equal(Get([]*Foo{foo, foo2}, "Bar.Name"), []string{"Test"}) 46 | is.Equal(Get([]*Foo{foo, foo2}, "Bar"), []*Bar{bar}) 47 | } 48 | 49 | func TestGetMap(t *testing.T) { 50 | is := assert.New(t) 51 | m := map[string]interface{}{ 52 | "bar": map[string]interface{}{ 53 | "name": "foobar", 54 | "example.com/hello": "world", 55 | }, 56 | } 57 | 58 | is.Equal("foobar", Get(m, "bar.name")) 59 | is.Equal("world", Get(m, `bar."example.com/hello"`)) 60 | is.Equal(nil, Get(m, "foo.name")) 61 | is.Equal(nil, Get(m, `foo."example.com/hello"`)) 62 | is.Equal([]interface{}{"dark", "dark"}, Get([]map[string]interface{}{m1, m2}, "firstname")) 63 | is.Equal([]interface{}{"test"}, Get([]map[string]interface{}{m1, m2}, "bar.name")) 64 | } 65 | 66 | func TestGetThroughInterface(t *testing.T) { 67 | is := assert.New(t) 68 | 69 | is.Equal(Get(foo, "BarInterface.Bars.Bar.Name"), []string{"Level2-1", "Level2-2"}) 70 | is.Equal(Get(foo, "BarPointer.Bars.Bar.Name"), []string{"Level2-1", "Level2-2"}) 71 | } 72 | 73 | func TestGetWithAllowZero(t *testing.T) { 74 | is := assert.New(t) 75 | 76 | var test []struct { 77 | Age int 78 | } 79 | 80 | for i := 0; i < 10; i++ { 81 | test = append(test, struct{ Age int }{Age: i}) 82 | } 83 | 84 | is.Equal(Get(test, "Age").([]int), []int{1, 2, 3, 4, 5, 6, 7, 8, 9}) 85 | is.Equal(Get(test, "Age", WithAllowZero()).([]int), []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}) 86 | } 87 | 88 | func TestGetNotFound(t *testing.T) { 89 | is := assert.New(t) 90 | 91 | is.Equal(nil, Get(foo, "id")) 92 | is.Equal(nil, Get(foo, "id.id")) 93 | is.Equal(nil, Get(foo, "Bar.id")) 94 | is.Equal(nil, Get(foo, "Bars.id")) 95 | } 96 | 97 | func TestGetSimple(t *testing.T) { 98 | is := assert.New(t) 99 | 100 | is.Equal(Get(foo, "ID"), 1) 101 | 102 | is.Equal(Get(foo, "Bar.Name"), "Test") 103 | 104 | result := Get(foo, "Bar.Bars.Name") 105 | 106 | is.Equal(result, []string{"Level1-1", "Level1-2"}) 107 | } 108 | 109 | func TestGetOrElse(t *testing.T) { 110 | is := assert.New(t) 111 | 112 | str := "hello world" 113 | is.Equal("hello world", GetOrElse(&str, "foobar")) 114 | is.Equal("hello world", GetOrElse(str, "foobar")) 115 | is.Equal("foobar", GetOrElse(nil, "foobar")) 116 | 117 | t.Run("nil with type", func(t *testing.T) { 118 | // test GetOrElse covers this case 119 | is.Equal("foobar", GetOrElse((*string)(nil), "foobar")) 120 | }) 121 | } 122 | 123 | func TestEmbeddedStructPointer(t *testing.T) { 124 | is := assert.New(t) 125 | 126 | root := RootStructPointer{} 127 | is.Equal(Get(root, "EmbeddedField"), nil) 128 | is.Equal(Get(root, "EmbeddedStruct.EmbeddedField"), nil) 129 | } 130 | 131 | func TestEmbeddedStructNotPointer(t *testing.T) { 132 | is := assert.New(t) 133 | 134 | root := RootStructNotPointer{} 135 | is.Equal(Get(root, "EmbeddedField"), nil) 136 | } 137 | -------------------------------------------------------------------------------- /scan.go: -------------------------------------------------------------------------------- 1 | package funk 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | // ForEach iterates over elements of collection and invokes iteratee 9 | // for each element. 10 | func ForEach(arr interface{}, predicate interface{}) { 11 | if !IsIteratee(arr) { 12 | panic("First parameter must be an iteratee") 13 | } 14 | 15 | var ( 16 | funcValue = reflect.ValueOf(predicate) 17 | arrValue = reflect.ValueOf(arr) 18 | arrType = arrValue.Type() 19 | funcType = funcValue.Type() 20 | ) 21 | 22 | if arrType.Kind() == reflect.Slice || arrType.Kind() == reflect.Array { 23 | if !IsFunction(predicate, 1, 0) { 24 | panic("Second argument must be a function with one parameter") 25 | } 26 | 27 | arrElemType := arrValue.Type().Elem() 28 | arrElemPointerType := reflect.New(arrElemType).Type() 29 | usePointer := arrElemPointerType.ConvertibleTo(funcType.In(0)) 30 | 31 | // Checking whether element type is convertible to function's first argument's type. 32 | if !arrElemType.ConvertibleTo(funcType.In(0)) && !usePointer { 33 | panic("Map function's argument is not compatible with type of array.") 34 | } 35 | 36 | for i := 0; i < arrValue.Len(); i++ { 37 | if usePointer { 38 | funcValue.Call([]reflect.Value{arrValue.Index(i).Addr()}) 39 | } else { 40 | funcValue.Call([]reflect.Value{arrValue.Index(i)}) 41 | } 42 | } 43 | } 44 | 45 | if arrType.Kind() == reflect.Map { 46 | if !IsFunction(predicate, 2, 0) { 47 | panic("Second argument must be a function with two parameters") 48 | } 49 | 50 | // Type checking for Map = (key, value) 51 | keyType := arrType.Key() 52 | valueType := arrType.Elem() 53 | 54 | if !keyType.ConvertibleTo(funcType.In(0)) { 55 | panic(fmt.Sprintf("function first argument is not compatible with %s", keyType.String())) 56 | } 57 | 58 | if !valueType.ConvertibleTo(funcType.In(1)) { 59 | panic(fmt.Sprintf("function second argument is not compatible with %s", valueType.String())) 60 | } 61 | 62 | for _, key := range arrValue.MapKeys() { 63 | funcValue.Call([]reflect.Value{key, arrValue.MapIndex(key)}) 64 | } 65 | } 66 | } 67 | 68 | // ForEachRight iterates over elements of collection from the right and invokes iteratee 69 | // for each element. 70 | func ForEachRight(arr interface{}, predicate interface{}) { 71 | if !IsIteratee(arr) { 72 | panic("First parameter must be an iteratee") 73 | } 74 | 75 | var ( 76 | funcValue = reflect.ValueOf(predicate) 77 | arrValue = reflect.ValueOf(arr) 78 | arrType = arrValue.Type() 79 | funcType = funcValue.Type() 80 | ) 81 | 82 | if arrType.Kind() == reflect.Slice || arrType.Kind() == reflect.Array { 83 | if !IsFunction(predicate, 1, 0) { 84 | panic("Second argument must be a function with one parameter") 85 | } 86 | 87 | arrElemType := arrValue.Type().Elem() 88 | arrElemPointerType := reflect.New(arrElemType).Type() 89 | usePointer := arrElemPointerType.ConvertibleTo(funcType.In(0)) 90 | 91 | // Checking whether element type is convertible to function's first argument's type. 92 | if !arrElemType.ConvertibleTo(funcType.In(0)) && !usePointer { 93 | panic("Map function's argument is not compatible with type of array.") 94 | } 95 | 96 | for i := arrValue.Len() - 1; i >= 0; i-- { 97 | if usePointer { 98 | funcValue.Call([]reflect.Value{arrValue.Index(i).Addr()}) 99 | } else { 100 | funcValue.Call([]reflect.Value{arrValue.Index(i)}) 101 | } 102 | 103 | } 104 | } 105 | 106 | if arrType.Kind() == reflect.Map { 107 | if !IsFunction(predicate, 2, 0) { 108 | panic("Second argument must be a function with two parameters") 109 | } 110 | 111 | // Type checking for Map = (key, value) 112 | keyType := arrType.Key() 113 | valueType := arrType.Elem() 114 | 115 | if !keyType.ConvertibleTo(funcType.In(0)) { 116 | panic(fmt.Sprintf("function first argument is not compatible with %s", keyType.String())) 117 | } 118 | 119 | if !valueType.ConvertibleTo(funcType.In(1)) { 120 | panic(fmt.Sprintf("function second argument is not compatible with %s", valueType.String())) 121 | } 122 | 123 | keys := Reverse(arrValue.MapKeys()).([]reflect.Value) 124 | 125 | for _, key := range keys { 126 | funcValue.Call([]reflect.Value{key, arrValue.MapIndex(key)}) 127 | } 128 | } 129 | } 130 | 131 | // Head gets the first element of array. 132 | func Head(arr interface{}) interface{} { 133 | value := redirectValue(reflect.ValueOf(arr)) 134 | valueType := value.Type() 135 | 136 | kind := value.Kind() 137 | 138 | if kind == reflect.Array || kind == reflect.Slice { 139 | if value.Len() == 0 { 140 | return nil 141 | } 142 | 143 | return value.Index(0).Interface() 144 | } 145 | 146 | panic(fmt.Sprintf("Type %s is not supported by Head", valueType.String())) 147 | } 148 | 149 | // Last gets the last element of array. 150 | func Last(arr interface{}) interface{} { 151 | value := redirectValue(reflect.ValueOf(arr)) 152 | valueType := value.Type() 153 | 154 | kind := value.Kind() 155 | 156 | if kind == reflect.Array || kind == reflect.Slice { 157 | if value.Len() == 0 { 158 | return nil 159 | } 160 | 161 | return value.Index(value.Len() - 1).Interface() 162 | } 163 | 164 | panic(fmt.Sprintf("Type %s is not supported by Last", valueType.String())) 165 | } 166 | 167 | // Initial gets all but the last element of array. 168 | func Initial(arr interface{}) interface{} { 169 | value := redirectValue(reflect.ValueOf(arr)) 170 | valueType := value.Type() 171 | 172 | kind := value.Kind() 173 | 174 | if kind == reflect.Array || kind == reflect.Slice { 175 | length := value.Len() 176 | 177 | if length <= 1 { 178 | return arr 179 | } 180 | 181 | return value.Slice(0, length-1).Interface() 182 | } 183 | 184 | panic(fmt.Sprintf("Type %s is not supported by Initial", valueType.String())) 185 | } 186 | 187 | // Tail gets all but the first element of array. 188 | func Tail(arr interface{}) interface{} { 189 | value := redirectValue(reflect.ValueOf(arr)) 190 | valueType := value.Type() 191 | 192 | kind := value.Kind() 193 | 194 | if kind == reflect.Array || kind == reflect.Slice { 195 | length := value.Len() 196 | 197 | if length <= 1 { 198 | return arr 199 | } 200 | 201 | return value.Slice(1, length).Interface() 202 | } 203 | 204 | panic(fmt.Sprintf("Type %s is not supported by Initial", valueType.String())) 205 | } 206 | -------------------------------------------------------------------------------- /scan_test.go: -------------------------------------------------------------------------------- 1 | package funk 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestForEach(t *testing.T) { 10 | is := assert.New(t) 11 | 12 | results := []int{} 13 | 14 | ForEach([]int{1, 2, 3, 4}, func(x int) { 15 | if x%2 == 0 { 16 | results = append(results, x) 17 | } 18 | }) 19 | 20 | is.Equal(results, []int{2, 4}) 21 | 22 | toModify := []int{1, 2, 3} 23 | ForEach(toModify, func(x *int) { *x = *x * 2 }) 24 | 25 | is.Equal(toModify, []int{2, 4, 6}) 26 | 27 | toModify = []int{} 28 | ForEach(toModify, func(x *int) {}) 29 | 30 | is.Equal(toModify, []int{}) 31 | 32 | strModify := []string{"a", "b"} 33 | ForEach(strModify, func(s *string) { 34 | *s = *s + *s 35 | }) 36 | 37 | is.Equal(strModify, []string{"aa", "bb"}) 38 | 39 | mapping := map[int]string{ 40 | 1: "Florent", 41 | 2: "Gilles", 42 | } 43 | 44 | ForEach(mapping, func(k int, v string) { 45 | is.Equal(v, mapping[k]) 46 | }) 47 | } 48 | 49 | func TestForEachRight(t *testing.T) { 50 | is := assert.New(t) 51 | 52 | results := []int{} 53 | 54 | ForEachRight([]int{1, 2, 3, 4}, func(x int) { 55 | results = append(results, x*2) 56 | }) 57 | 58 | is.Equal(results, []int{8, 6, 4, 2}) 59 | 60 | toModify := []int{1, 2, 3} 61 | ForEach(toModify, func(x *int) { *x = *x * 2 }) 62 | 63 | is.Equal(toModify, []int{2, 4, 6}) 64 | 65 | toModify = []int{} 66 | ForEach(toModify, func(x *int) {}) 67 | 68 | is.Equal(toModify, []int{}) 69 | 70 | strModify := []string{"a", "b"} 71 | ForEach(strModify, func(s *string) { 72 | *s = *s + *s 73 | }) 74 | 75 | is.Equal(strModify, []string{"aa", "bb"}) 76 | 77 | mapping := map[int]string{ 78 | 1: "Florent", 79 | 2: "Gilles", 80 | } 81 | 82 | mapKeys := []int{} 83 | 84 | ForEachRight(mapping, func(k int, v string) { 85 | is.Equal(v, mapping[k]) 86 | mapKeys = append(mapKeys, k) 87 | }) 88 | 89 | is.Equal(len(mapKeys), 2) 90 | is.Contains(mapKeys, 1) 91 | is.Contains(mapKeys, 2) 92 | } 93 | 94 | func TestHead(t *testing.T) { 95 | is := assert.New(t) 96 | 97 | is.Equal(Head([]int{1, 2, 3, 4}), 1) 98 | } 99 | 100 | func TestLast(t *testing.T) { 101 | is := assert.New(t) 102 | 103 | is.Equal(Last([]int{1, 2, 3, 4}), 4) 104 | } 105 | 106 | func TestTail(t *testing.T) { 107 | is := assert.New(t) 108 | 109 | is.Equal(Tail([]int{}), []int{}) 110 | is.Equal(Tail([]int{1}), []int{1}) 111 | is.Equal(Tail([]int{1, 2, 3, 4}), []int{2, 3, 4}) 112 | } 113 | 114 | func TestInitial(t *testing.T) { 115 | is := assert.New(t) 116 | 117 | is.Equal(Initial([]int{}), []int{}) 118 | is.Equal(Initial([]int{1}), []int{1}) 119 | is.Equal(Initial([]int{1, 2, 3, 4}), []int{1, 2, 3}) 120 | } 121 | -------------------------------------------------------------------------------- /short_if.go: -------------------------------------------------------------------------------- 1 | package funk 2 | 3 | func ShortIf(condition bool, a interface{}, b interface{}) interface{} { 4 | if condition { 5 | return a 6 | } 7 | return b 8 | } 9 | -------------------------------------------------------------------------------- /short_if_test.go: -------------------------------------------------------------------------------- 1 | package funk 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestShortIf(t *testing.T) { 10 | is := assert.New(t) 11 | 12 | r := ShortIf(10 > 5, 10, 5) 13 | is.Equal(r, 10) 14 | 15 | r = ShortIf(10.0 == 10, "yes", "no") 16 | is.Equal(r, "yes") 17 | 18 | r = ShortIf('a' == 'b', "equal chars", "unequal chars") 19 | is.Equal(r, "unequal chars") 20 | 21 | r = ShortIf(true, "Same string", "Different strings") 22 | is.Equal(r, "Same string") 23 | 24 | type testStruct struct{} 25 | a := testStruct{} 26 | b := testStruct{} 27 | r = ShortIf(a == b, &a, &b) 28 | is.Equal(r, &b) 29 | 30 | } 31 | -------------------------------------------------------------------------------- /subset.go: -------------------------------------------------------------------------------- 1 | package funk 2 | 3 | import ( 4 | "reflect" 5 | ) 6 | 7 | // Subset returns true if collection x is a subset of y. 8 | func Subset(x interface{}, y interface{}) bool { 9 | if !IsCollection(x) { 10 | panic("First parameter must be a collection") 11 | } 12 | if !IsCollection(y) { 13 | panic("Second parameter must be a collection") 14 | } 15 | 16 | xValue := reflect.ValueOf(x) 17 | xType := xValue.Type() 18 | 19 | yValue := reflect.ValueOf(y) 20 | yType := yValue.Type() 21 | 22 | if NotEqual(xType, yType) { 23 | panic("Parameters must have the same type") 24 | } 25 | 26 | if xValue.Len() == 0 { 27 | return true 28 | } 29 | 30 | if yValue.Len() == 0 || yValue.Len() < xValue.Len() { 31 | return false 32 | } 33 | 34 | for i := 0; i < xValue.Len(); i++ { 35 | if !Contains(yValue.Interface(), xValue.Index(i).Interface()) { 36 | return false 37 | } 38 | } 39 | 40 | return true 41 | } 42 | -------------------------------------------------------------------------------- /subset_test.go: -------------------------------------------------------------------------------- 1 | package funk 2 | 3 | import ( 4 | "testing" 5 | "github.com/stretchr/testify/assert" 6 | ) 7 | 8 | func TestSubset(t *testing.T) { 9 | is := assert.New(t) 10 | 11 | r := Subset([]int{1, 2, 4}, []int{1, 2, 3, 4, 5}) 12 | is.True(r) 13 | 14 | r = Subset([]string{"foo", "bar"},[]string{"foo", "bar", "hello", "bar", "hi"}) 15 | is.True(r) 16 | 17 | r = Subset([]string{"foo", "bar","extra"},[]string{"foo", "bar", "hello", "bar", "hi"}) 18 | is.False(r) 19 | 20 | r = Subset([]string{"hello", "foo", "bar", "hello", "bar", "hi"}, []string{}) 21 | is.False(r) 22 | 23 | r = Subset([]string{}, []string{"hello", "foo", "bar", "hello", "bar", "hi"}) 24 | is.True(r) 25 | 26 | r = Subset([]string{}, []string{}) 27 | is.True(r) 28 | 29 | r = Subset([]string{}, []string{"hello"}) 30 | is.True(r) 31 | 32 | r = Subset([]string{"hello", "foo", "bar", "hello", "bar", "hi"}, []string{"foo", "bar"} ) 33 | is.False(r) 34 | } 35 | -------------------------------------------------------------------------------- /subtraction.go: -------------------------------------------------------------------------------- 1 | package funk 2 | 3 | import ( 4 | "reflect" 5 | ) 6 | 7 | // Subtract returns the subtraction between two collections. 8 | func Subtract(x interface{}, y interface{}) interface{} { 9 | if !IsCollection(x) { 10 | panic("First parameter must be a collection") 11 | } 12 | if !IsCollection(y) { 13 | panic("Second parameter must be a collection") 14 | } 15 | 16 | hash := map[interface{}]struct{}{} 17 | 18 | xValue := reflect.ValueOf(x) 19 | xType := xValue.Type() 20 | 21 | yValue := reflect.ValueOf(y) 22 | yType := yValue.Type() 23 | 24 | if NotEqual(xType, yType) { 25 | panic("Parameters must have the same type") 26 | } 27 | 28 | zType := reflect.SliceOf(xType.Elem()) 29 | zSlice := reflect.MakeSlice(zType, 0, 0) 30 | 31 | for i := 0; i < xValue.Len(); i++ { 32 | v := xValue.Index(i).Interface() 33 | hash[v] = struct{}{} 34 | } 35 | 36 | for i := 0; i < yValue.Len(); i++ { 37 | v := yValue.Index(i).Interface() 38 | _, ok := hash[v] 39 | if ok { 40 | delete(hash, v) 41 | } 42 | } 43 | 44 | for i := 0; i < xValue.Len(); i++ { 45 | v := xValue.Index(i).Interface() 46 | _, ok := hash[v] 47 | if ok { 48 | zSlice = reflect.Append(zSlice, xValue.Index(i)) 49 | } 50 | } 51 | 52 | return zSlice.Interface() 53 | } 54 | 55 | // SubtractString returns the subtraction between two collections of string 56 | func SubtractString(x []string, y []string) []string { 57 | if len(x) == 0 { 58 | return []string{} 59 | } 60 | 61 | if len(y) == 0 { 62 | return x 63 | } 64 | 65 | slice := []string{} 66 | hash := map[string]struct{}{} 67 | 68 | for _, v := range x { 69 | hash[v] = struct{}{} 70 | } 71 | 72 | for _, v := range y { 73 | _, ok := hash[v] 74 | if ok { 75 | delete(hash, v) 76 | } 77 | } 78 | 79 | for _, v := range x { 80 | _, ok := hash[v] 81 | if ok { 82 | slice = append(slice, v) 83 | } 84 | } 85 | 86 | return slice 87 | } 88 | -------------------------------------------------------------------------------- /subtraction_test.go: -------------------------------------------------------------------------------- 1 | package funk 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestSubtract(t *testing.T) { 10 | is := assert.New(t) 11 | 12 | r := Subtract([]int{1, 2, 3, 4, 5}, []int{2, 4, 6}) 13 | is.Equal([]int{1, 3, 5}, r) 14 | 15 | r = Subtract([]string{"foo", "bar", "hello", "bar", "hi"}, []string{"foo", "bar"}) 16 | is.Equal([]string{"hello", "hi"}, r) 17 | 18 | r = Subtract([]string{"hello", "foo", "bar", "hello", "bar", "hi"}, []string{"foo", "bar"}) 19 | is.Equal([]string{"hello", "hello", "hi"}, r) 20 | 21 | r = Subtract([]int{1, 2, 3, 4, 5}, []int{}) 22 | is.Equal([]int{1, 2, 3, 4, 5}, r) 23 | 24 | r = Subtract([]string{"bar", "bar"}, []string{}) 25 | is.Equal([]string{"bar", "bar"}, r) 26 | } 27 | 28 | func TestSubtractString(t *testing.T) { 29 | is := assert.New(t) 30 | 31 | r := SubtractString([]string{"foo", "bar", "hello", "bar"}, []string{"foo", "bar"}) 32 | is.Equal([]string{"hello"}, r) 33 | 34 | r = SubtractString([]string{"foo", "bar", "hello", "bar", "world", "world"}, []string{"foo", "bar"}) 35 | is.Equal([]string{"hello", "world", "world"}, r) 36 | 37 | r = SubtractString([]string{"bar", "bar"}, []string{}) 38 | is.Equal([]string{"bar", "bar"}, r) 39 | 40 | r = SubtractString([]string{}, []string{"bar", "bar"}) 41 | is.Equal([]string{}, r) 42 | } 43 | -------------------------------------------------------------------------------- /transform.go: -------------------------------------------------------------------------------- 1 | package funk 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "reflect" 7 | "strings" 8 | ) 9 | 10 | // Chunk creates an array of elements split into groups with the length of size. 11 | // If array can't be split evenly, the final chunk will be 12 | // the remaining element. 13 | func Chunk(arr interface{}, size int) interface{} { 14 | if !IsIteratee(arr) { 15 | panic("First parameter must be neither array nor slice") 16 | } 17 | 18 | if size == 0 { 19 | return arr 20 | } 21 | 22 | arrValue := reflect.ValueOf(arr) 23 | 24 | arrType := arrValue.Type() 25 | 26 | resultSliceType := reflect.SliceOf(arrType) 27 | 28 | // Initialize final result slice which will contains slice 29 | resultSlice := reflect.MakeSlice(resultSliceType, 0, 0) 30 | 31 | itemType := arrType.Elem() 32 | 33 | var itemSlice reflect.Value 34 | 35 | itemSliceType := reflect.SliceOf(itemType) 36 | 37 | length := arrValue.Len() 38 | 39 | for i := 0; i < length; i++ { 40 | if i%size == 0 || i == 0 { 41 | if itemSlice.Kind() != reflect.Invalid { 42 | resultSlice = reflect.Append(resultSlice, itemSlice) 43 | } 44 | 45 | itemSlice = reflect.MakeSlice(itemSliceType, 0, 0) 46 | } 47 | 48 | itemSlice = reflect.Append(itemSlice, arrValue.Index(i)) 49 | 50 | if i == length-1 { 51 | resultSlice = reflect.Append(resultSlice, itemSlice) 52 | } 53 | } 54 | 55 | return resultSlice.Interface() 56 | } 57 | 58 | // ToMap transforms a collection of instances to a Map. 59 | // []T => map[type of T.]T 60 | func ToMap(in interface{}, pivot string) interface{} { 61 | // input value must be a collection 62 | if !IsCollection(in) { 63 | panic(fmt.Sprintf("%v must be a slict or an array", in)) 64 | } 65 | 66 | value := reflect.ValueOf(in) 67 | 68 | inType := value.Type() 69 | structType := inType.Elem() 70 | 71 | // retrieve the struct in the slice to deduce key type 72 | if structType.Kind() == reflect.Ptr { 73 | structType = structType.Elem() 74 | } 75 | 76 | field, ok := structType.FieldByName(pivot) 77 | if !ok { 78 | panic(fmt.Sprintf("`%s` must be a field of the struct %s", pivot, structType.Name())) 79 | } 80 | 81 | // value of the map will be the input type 82 | collectionType := reflect.MapOf(field.Type, inType.Elem()) 83 | 84 | // create a map from scratch 85 | collection := reflect.MakeMap(collectionType) 86 | 87 | for i := 0; i < value.Len(); i++ { 88 | instance := value.Index(i) 89 | var field reflect.Value 90 | 91 | if instance.Kind() == reflect.Ptr { 92 | field = instance.Elem().FieldByName(pivot) 93 | } else { 94 | field = instance.FieldByName(pivot) 95 | } 96 | 97 | collection.SetMapIndex(field, instance) 98 | } 99 | 100 | return collection.Interface() 101 | } 102 | 103 | // ToSet transforms a collection of instances to a Set. 104 | // []T => map[T]struct{} 105 | func ToSet(in interface{}) interface{} { 106 | // input value must be a collection 107 | if !IsCollection(in) { 108 | panic(fmt.Sprintf("%v must be a slice or an array", in)) 109 | } 110 | 111 | var ( 112 | empty = struct{}{} 113 | emptyType = reflect.TypeOf(empty) 114 | emptyValue = reflect.ValueOf(empty) 115 | ) 116 | 117 | value := reflect.ValueOf(in) 118 | elemType := value.Type().Elem() 119 | 120 | // key of the set will be the input type 121 | collection := reflect.MakeMap(reflect.MapOf(elemType, emptyType)) 122 | 123 | for i := 0; i < value.Len(); i++ { 124 | collection.SetMapIndex(value.Index(i), emptyValue) 125 | } 126 | 127 | return collection.Interface() 128 | } 129 | 130 | func mapSlice(arrValue reflect.Value, funcValue reflect.Value) reflect.Value { 131 | funcType := funcValue.Type() 132 | 133 | if funcType.NumIn() != 1 || funcType.NumOut() == 0 || funcType.NumOut() > 2 { 134 | panic("Map function with an array must have one parameter and must return one or two parameters") 135 | } 136 | 137 | arrElemType := arrValue.Type().Elem() 138 | 139 | // Checking whether element type is convertible to function's first argument's type. 140 | if !arrElemType.ConvertibleTo(funcType.In(0)) { 141 | panic("Map function's argument is not compatible with type of array.") 142 | } 143 | 144 | if funcType.NumOut() == 1 { 145 | // Get slice type corresponding to function's return value's type. 146 | resultSliceType := reflect.SliceOf(funcType.Out(0)) 147 | 148 | // MakeSlice takes a slice kind type, and makes a slice. 149 | resultSlice := reflect.MakeSlice(resultSliceType, 0, 0) 150 | 151 | for i := 0; i < arrValue.Len(); i++ { 152 | result := funcValue.Call([]reflect.Value{arrValue.Index(i)})[0] 153 | 154 | resultSlice = reflect.Append(resultSlice, result) 155 | } 156 | 157 | return resultSlice 158 | } 159 | 160 | if funcType.NumOut() == 2 { 161 | // value of the map will be the input type 162 | collectionType := reflect.MapOf(funcType.Out(0), funcType.Out(1)) 163 | 164 | // create a map from scratch 165 | collection := reflect.MakeMap(collectionType) 166 | 167 | for i := 0; i < arrValue.Len(); i++ { 168 | results := funcValue.Call([]reflect.Value{arrValue.Index(i)}) 169 | 170 | collection.SetMapIndex(results[0], results[1]) 171 | } 172 | 173 | return collection 174 | } 175 | 176 | return reflect.Value{} 177 | } 178 | 179 | func mapMap(arrValue reflect.Value, funcValue reflect.Value) reflect.Value { 180 | funcType := funcValue.Type() 181 | 182 | if funcType.NumIn() != 2 || funcType.NumOut() == 0 || funcType.NumOut() > 2 { 183 | panic("Map function with a map must have two parameters and must return one or two parameters") 184 | } 185 | 186 | // Only one returned parameter, should be a slice 187 | if funcType.NumOut() == 1 { 188 | // Get slice type corresponding to function's return value's type. 189 | resultSliceType := reflect.SliceOf(funcType.Out(0)) 190 | 191 | // MakeSlice takes a slice kind type, and makes a slice. 192 | resultSlice := reflect.MakeSlice(resultSliceType, 0, 0) 193 | 194 | for _, key := range arrValue.MapKeys() { 195 | results := funcValue.Call([]reflect.Value{key, arrValue.MapIndex(key)}) 196 | 197 | result := results[0] 198 | 199 | resultSlice = reflect.Append(resultSlice, result) 200 | } 201 | 202 | return resultSlice 203 | } 204 | 205 | // two parameters, should be a map 206 | if funcType.NumOut() == 2 { 207 | // value of the map will be the input type 208 | collectionType := reflect.MapOf(funcType.Out(0), funcType.Out(1)) 209 | 210 | // create a map from scratch 211 | collection := reflect.MakeMap(collectionType) 212 | 213 | for _, key := range arrValue.MapKeys() { 214 | results := funcValue.Call([]reflect.Value{key, arrValue.MapIndex(key)}) 215 | 216 | collection.SetMapIndex(results[0], results[1]) 217 | 218 | } 219 | 220 | return collection 221 | } 222 | 223 | return reflect.Value{} 224 | } 225 | 226 | // Map manipulates an iteratee and transforms it to another type. 227 | func Map(arr interface{}, mapFunc interface{}) interface{} { 228 | result := mapFn(arr, mapFunc, "Map") 229 | 230 | if result.IsValid() { 231 | return result.Interface() 232 | } 233 | 234 | return nil 235 | } 236 | 237 | func mapFn(arr interface{}, mapFunc interface{}, funcName string) reflect.Value { 238 | if !IsIteratee(arr) { 239 | panic("First parameter must be an iteratee") 240 | } 241 | 242 | if !IsFunction(mapFunc) { 243 | panic("Second argument must be function") 244 | } 245 | 246 | var ( 247 | funcValue = reflect.ValueOf(mapFunc) 248 | arrValue = reflect.ValueOf(arr) 249 | arrType = arrValue.Type() 250 | ) 251 | 252 | kind := arrType.Kind() 253 | 254 | if kind == reflect.Slice || kind == reflect.Array { 255 | return mapSlice(arrValue, funcValue) 256 | } else if kind == reflect.Map { 257 | return mapMap(arrValue, funcValue) 258 | } 259 | 260 | panic(fmt.Sprintf("Type %s is not supported by "+funcName, arrType.String())) 261 | } 262 | 263 | // FlatMap manipulates an iteratee and transforms it to a flattened collection of another type. 264 | func FlatMap(arr interface{}, mapFunc interface{}) interface{} { 265 | result := mapFn(arr, mapFunc, "FlatMap") 266 | 267 | if result.IsValid() { 268 | return flatten(result).Interface() 269 | } 270 | 271 | return nil 272 | } 273 | 274 | // Flatten flattens a two-dimensional array. 275 | func Flatten(out interface{}) interface{} { 276 | return flatten(reflect.ValueOf(out)).Interface() 277 | } 278 | 279 | func flatten(value reflect.Value) reflect.Value { 280 | sliceType := value.Type() 281 | 282 | if (value.Kind() != reflect.Slice && value.Kind() != reflect.Array) || 283 | (sliceType.Elem().Kind() != reflect.Slice && sliceType.Elem().Kind() != reflect.Array) { 284 | panic("Argument must be an array or slice of at least two dimensions") 285 | } 286 | 287 | resultSliceType := sliceType.Elem().Elem() 288 | 289 | resultSlice := reflect.MakeSlice(reflect.SliceOf(resultSliceType), 0, 0) 290 | 291 | length := value.Len() 292 | 293 | for i := 0; i < length; i++ { 294 | item := value.Index(i) 295 | 296 | resultSlice = reflect.AppendSlice(resultSlice, item) 297 | } 298 | 299 | return resultSlice 300 | } 301 | 302 | // FlattenDeep recursively flattens array. 303 | func FlattenDeep(out interface{}) interface{} { 304 | return flattenDeep(reflect.ValueOf(out)).Interface() 305 | } 306 | 307 | func flattenDeep(value reflect.Value) reflect.Value { 308 | sliceType := sliceElem(value.Type()) 309 | 310 | resultSlice := reflect.MakeSlice(reflect.SliceOf(sliceType), 0, 0) 311 | 312 | return flattenRecursive(value, resultSlice) 313 | } 314 | 315 | func flattenRecursive(value reflect.Value, result reflect.Value) reflect.Value { 316 | length := value.Len() 317 | 318 | for i := 0; i < length; i++ { 319 | item := value.Index(i) 320 | kind := item.Kind() 321 | 322 | if kind == reflect.Slice || kind == reflect.Array { 323 | result = flattenRecursive(item, result) 324 | } else { 325 | result = reflect.Append(result, item) 326 | } 327 | } 328 | 329 | return result 330 | } 331 | 332 | // Shuffle creates an array of shuffled values 333 | func Shuffle(in interface{}) interface{} { 334 | value := reflect.ValueOf(in) 335 | valueType := value.Type() 336 | 337 | kind := value.Kind() 338 | 339 | if kind == reflect.Array || kind == reflect.Slice { 340 | length := value.Len() 341 | 342 | resultSlice := makeSlice(value, length) 343 | 344 | for i, v := range rand.Perm(length) { 345 | resultSlice.Index(i).Set(value.Index(v)) 346 | } 347 | 348 | return resultSlice.Interface() 349 | } 350 | 351 | panic(fmt.Sprintf("Type %s is not supported by Shuffle", valueType.String())) 352 | } 353 | 354 | // Reverse transforms an array the first element will become the last, 355 | // the second element will become the second to last, etc. 356 | func Reverse(in interface{}) interface{} { 357 | value := reflect.ValueOf(in) 358 | valueType := value.Type() 359 | 360 | kind := value.Kind() 361 | 362 | if kind == reflect.String { 363 | return ReverseString(in.(string)) 364 | } 365 | 366 | if kind == reflect.Array || kind == reflect.Slice { 367 | length := value.Len() 368 | 369 | resultSlice := makeSlice(value, length) 370 | 371 | j := 0 372 | for i := length - 1; i >= 0; i-- { 373 | resultSlice.Index(j).Set(value.Index(i)) 374 | j++ 375 | } 376 | 377 | return resultSlice.Interface() 378 | } 379 | 380 | panic(fmt.Sprintf("Type %s is not supported by Reverse", valueType.String())) 381 | } 382 | 383 | // Uniq creates an array with unique values. 384 | func Uniq(in interface{}) interface{} { 385 | value := reflect.ValueOf(in) 386 | valueType := value.Type() 387 | 388 | kind := value.Kind() 389 | 390 | if kind == reflect.Array || kind == reflect.Slice { 391 | length := value.Len() 392 | 393 | result := makeSlice(value, 0) 394 | 395 | seen := make(map[interface{}]bool, length) 396 | 397 | for i := 0; i < length; i++ { 398 | val := value.Index(i) 399 | v := val.Interface() 400 | 401 | if _, ok := seen[v]; ok { 402 | continue 403 | } 404 | 405 | seen[v] = true 406 | result = reflect.Append(result, val) 407 | } 408 | 409 | return result.Interface() 410 | } 411 | 412 | panic(fmt.Sprintf("Type %s is not supported by Uniq", valueType.String())) 413 | } 414 | 415 | // Uniq creates an array with unique values. 416 | func UniqBy(in interface{}, mapFunc interface{}) interface{} { 417 | if !IsFunction(mapFunc) { 418 | panic("Second argument must be function") 419 | } 420 | 421 | value := reflect.ValueOf(in) 422 | valueType := value.Type() 423 | 424 | kind := value.Kind() 425 | 426 | funcValue := reflect.ValueOf(mapFunc) 427 | 428 | if kind == reflect.Array || kind == reflect.Slice { 429 | length := value.Len() 430 | 431 | result := makeSlice(value, 0) 432 | 433 | seen := make(map[interface{}]bool, length) 434 | 435 | for i := 0; i < length; i++ { 436 | val := value.Index(i) 437 | v := funcValue.Call([]reflect.Value{val})[0].Interface() 438 | 439 | if _, ok := seen[v]; ok { 440 | continue 441 | } 442 | 443 | seen[v] = true 444 | result = reflect.Append(result, val) 445 | } 446 | 447 | return result.Interface() 448 | } 449 | 450 | panic(fmt.Sprintf("Type %s is not supported by Uniq", valueType.String())) 451 | } 452 | 453 | // ConvertSlice converts a slice type to another, 454 | // a perfect example would be to convert a slice of struct to a slice of interface. 455 | func ConvertSlice(in interface{}, out interface{}) { 456 | srcValue := reflect.ValueOf(in) 457 | 458 | dstValue := reflect.ValueOf(out) 459 | 460 | if dstValue.Kind() != reflect.Ptr { 461 | panic("Second argument must be a pointer") 462 | } 463 | 464 | dstValue = dstValue.Elem() 465 | 466 | if srcValue.Kind() != reflect.Slice && srcValue.Kind() != reflect.Array { 467 | panic("First argument must be an array or slice") 468 | } 469 | 470 | if dstValue.Kind() != reflect.Slice && dstValue.Kind() != reflect.Array { 471 | panic("Second argument must be an array or slice") 472 | } 473 | 474 | // returns value that points to dstValue 475 | direct := reflect.Indirect(dstValue) 476 | 477 | length := srcValue.Len() 478 | 479 | for i := 0; i < length; i++ { 480 | dstValue = reflect.Append(dstValue, srcValue.Index(i)) 481 | } 482 | 483 | direct.Set(dstValue) 484 | } 485 | 486 | // Drop creates an array/slice with `n` elements dropped from the beginning. 487 | func Drop(in interface{}, n int) interface{} { 488 | value := reflect.ValueOf(in) 489 | valueType := value.Type() 490 | 491 | kind := value.Kind() 492 | 493 | if kind == reflect.Array || kind == reflect.Slice { 494 | length := value.Len() 495 | 496 | resultSlice := makeSlice(value, length-n) 497 | 498 | j := 0 499 | for i := n; i < length; i++ { 500 | resultSlice.Index(j).Set(value.Index(i)) 501 | j++ 502 | } 503 | 504 | return resultSlice.Interface() 505 | 506 | } 507 | 508 | panic(fmt.Sprintf("Type %s is not supported by Drop", valueType.String())) 509 | } 510 | 511 | // Prune returns a copy of "in" that only contains fields in "paths" 512 | // which are looked up using struct field name. 513 | // For lookup paths by field tag instead, use funk.PruneByTag() 514 | func Prune(in interface{}, paths []string) (interface{}, error) { 515 | return pruneByTag(in, paths, nil /*tag*/) 516 | } 517 | 518 | // pruneByTag returns a copy of "in" that only contains fields in "paths" 519 | // which are looked up using struct field Tag "tag". 520 | func PruneByTag(in interface{}, paths []string, tag string) (interface{}, error) { 521 | return pruneByTag(in, paths, &tag) 522 | } 523 | 524 | // pruneByTag returns a copy of "in" that only contains fields in "paths" 525 | // which are looked up using struct field Tag "tag". If tag is nil, 526 | // traverse paths using struct field name 527 | func pruneByTag(in interface{}, paths []string, tag *string) (interface{}, error) { 528 | inValue := reflect.ValueOf(in) 529 | 530 | ret := reflect.New(inValue.Type()).Elem() 531 | 532 | for _, path := range paths { 533 | parts := strings.Split(path, ".") 534 | if err := prune(inValue, ret, parts, tag); err != nil { 535 | return nil, err 536 | } 537 | } 538 | return ret.Interface(), nil 539 | } 540 | 541 | func prune(inValue reflect.Value, ret reflect.Value, parts []string, tag *string) error { 542 | if len(parts) == 0 { 543 | // we reached the location that ret needs to hold inValue 544 | // Note: The value at the end of the path is not copied, maybe we need to change. 545 | // ret and the original data holds the same reference to this value 546 | ret.Set(inValue) 547 | return nil 548 | } 549 | 550 | inKind := inValue.Kind() 551 | 552 | switch inKind { 553 | case reflect.Ptr: 554 | if inValue.IsNil() { 555 | // TODO validate 556 | return nil 557 | } 558 | if ret.IsNil() { 559 | // init ret and go to next level 560 | ret.Set(reflect.New(inValue.Type().Elem())) 561 | } 562 | return prune(inValue.Elem(), ret.Elem(), parts, tag) 563 | case reflect.Struct: 564 | part := parts[0] 565 | var fValue reflect.Value 566 | var fRet reflect.Value 567 | if tag == nil { 568 | // use field name 569 | fValue = inValue.FieldByName(part) 570 | if !fValue.IsValid() { 571 | return fmt.Errorf("field name %v is not found in struct %v", part, inValue.Type().String()) 572 | } 573 | fRet = ret.FieldByName(part) 574 | } else { 575 | // search tag that has key equal to part 576 | found := false 577 | for i := 0; i < inValue.NumField(); i++ { 578 | f := inValue.Type().Field(i) 579 | if key, ok := f.Tag.Lookup(*tag); ok { 580 | if key == part { 581 | fValue = inValue.Field(i) 582 | fRet = ret.Field(i) 583 | found = true 584 | break 585 | } 586 | } 587 | } 588 | if !found { 589 | return fmt.Errorf("struct tag %v is not found with key %v", *tag, part) 590 | } 591 | } 592 | // init Ret is zero and go down one more level 593 | if fRet.IsZero() { 594 | fRet.Set(reflect.New(fValue.Type()).Elem()) 595 | } 596 | return prune(fValue, fRet, parts[1:], tag) 597 | case reflect.Array, reflect.Slice: 598 | // set all its elements 599 | length := inValue.Len() 600 | // init ret 601 | if ret.IsZero() { 602 | if inKind == reflect.Slice { 603 | ret.Set(reflect.MakeSlice(inValue.Type(), length /*len*/, length /*cap*/)) 604 | } else { // array 605 | ret.Set(reflect.New(inValue.Type()).Elem()) 606 | } 607 | } 608 | for j := 0; j < length; j++ { 609 | if err := prune(inValue.Index(j), ret.Index(j), parts, tag); err != nil { 610 | return err 611 | } 612 | } 613 | default: 614 | return fmt.Errorf("path %v cannot be looked up on kind of %v", strings.Join(parts, "."), inValue.Kind()) 615 | } 616 | 617 | return nil 618 | } 619 | -------------------------------------------------------------------------------- /transform_test.go: -------------------------------------------------------------------------------- 1 | package funk 2 | 3 | import ( 4 | "database/sql" 5 | "fmt" 6 | "reflect" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | "github.com/stretchr/testify/require" 11 | ) 12 | 13 | func TestMap(t *testing.T) { 14 | is := assert.New(t) 15 | 16 | r := Map([]int{1, 2, 3, 4}, func(x int) string { 17 | return "Hello" 18 | }) 19 | 20 | result, ok := r.([]string) 21 | 22 | is.True(ok) 23 | is.Equal(len(result), 4) 24 | 25 | r = Map([]int{1, 2, 3, 4}, func(x int) (int, int) { 26 | return x, x 27 | }) 28 | 29 | resultType := reflect.TypeOf(r) 30 | 31 | is.True(resultType.Kind() == reflect.Map) 32 | is.True(resultType.Key().Kind() == reflect.Int) 33 | is.True(resultType.Elem().Kind() == reflect.Int) 34 | 35 | mapping := map[int]string{ 36 | 1: "Florent", 37 | 2: "Gilles", 38 | } 39 | 40 | r = Map(mapping, func(k int, v string) int { 41 | return k 42 | }) 43 | 44 | is.True(reflect.TypeOf(r).Kind() == reflect.Slice) 45 | is.True(reflect.TypeOf(r).Elem().Kind() == reflect.Int) 46 | 47 | r = Map(mapping, func(k int, v string) (string, string) { 48 | return fmt.Sprintf("%d", k), v 49 | }) 50 | 51 | resultType = reflect.TypeOf(r) 52 | 53 | is.True(resultType.Kind() == reflect.Map) 54 | is.True(resultType.Key().Kind() == reflect.String) 55 | is.True(resultType.Elem().Kind() == reflect.String) 56 | } 57 | 58 | func TestFlatMap(t *testing.T) { 59 | is := assert.New(t) 60 | 61 | x := reflect.Value{}.IsValid() 62 | fmt.Println(x) 63 | 64 | r := FlatMap([][]int{{1}, {2}, {3}, {4}}, func(x []int) []int { 65 | return x 66 | }) 67 | 68 | result, ok := r.([]int) 69 | 70 | is.True(ok) 71 | is.ElementsMatch(result, []int{1, 2, 3, 4}) 72 | 73 | mapping := map[string][]int{ 74 | "a": {1}, 75 | "b": {2}, 76 | } 77 | 78 | r = FlatMap(mapping, func(k string, v []int) []int { 79 | return v 80 | }) 81 | 82 | result, ok = r.([]int) 83 | 84 | is.True(ok) 85 | is.ElementsMatch(result, []int{1, 2}) 86 | } 87 | 88 | func TestToMap(t *testing.T) { 89 | is := assert.New(t) 90 | 91 | f1 := Foo{ 92 | ID: 1, 93 | FirstName: "Dark", 94 | LastName: "Vador", 95 | Age: 30, 96 | Bar: &Bar{ 97 | Name: "Test", 98 | }, 99 | } 100 | 101 | f2 := Foo{ 102 | ID: 1, 103 | FirstName: "Light", 104 | LastName: "Vador", 105 | Age: 30, 106 | Bar: &Bar{ 107 | Name: "Test", 108 | }, 109 | } 110 | 111 | // []*Foo -> Map 112 | sliceResults := []*Foo{&f1, &f2} 113 | 114 | instanceMapByID := ToMap(sliceResults, "ID") 115 | is.True(reflect.TypeOf(instanceMapByID).Kind() == reflect.Map) 116 | 117 | mappingByID, ok := instanceMapByID.(map[int]*Foo) 118 | is.True(ok) 119 | is.True(len(mappingByID) == 1) 120 | 121 | for _, result := range sliceResults { 122 | item, ok := mappingByID[result.ID] 123 | 124 | is.True(ok) 125 | is.True(reflect.TypeOf(item).Kind() == reflect.Ptr) 126 | is.True(reflect.TypeOf(item).Elem().Kind() == reflect.Struct) 127 | 128 | is.Equal(item.ID, result.ID) 129 | } 130 | 131 | // Array -> Map 132 | arrayResults := [4]Foo{f1, f1, f2, f2} 133 | 134 | instanceMapByFirstName := ToMap(arrayResults, "FirstName") 135 | is.True(reflect.TypeOf(instanceMapByFirstName).Kind() == reflect.Map) 136 | 137 | mappingByFirstName, ok := instanceMapByFirstName.(map[string]Foo) 138 | is.True(ok) 139 | is.True(len(mappingByFirstName) == 2) 140 | 141 | for _, result := range arrayResults { 142 | item, ok := mappingByFirstName[result.FirstName] 143 | 144 | is.True(ok) 145 | is.True(reflect.TypeOf(item).Kind() == reflect.Struct) 146 | 147 | is.Equal(item.FirstName, result.FirstName) 148 | } 149 | } 150 | 151 | func TestToSet(t *testing.T) { 152 | is := assert.New(t) 153 | 154 | type Foo struct { 155 | ID int 156 | Name string 157 | } 158 | 159 | var ( 160 | f1 = Foo{ID: 1, Name: "hello"} 161 | f2 = Foo{ID: 1, Name: "hello"} 162 | ) 163 | 164 | // [2]Foo -> map[Foo]struct{} 165 | array := [2]Foo{f1, f2} 166 | 167 | resultOfArray := ToSet(array) 168 | is.True(reflect.TypeOf(resultOfArray).Kind() == reflect.Map) 169 | 170 | setFromArray, ok := resultOfArray.(map[Foo]struct{}) 171 | is.True(ok) 172 | is.True(len(setFromArray) == 1) 173 | 174 | for k, v := range setFromArray { 175 | is.True(reflect.TypeOf(v).Size() == 0) 176 | is.True(k == f1) 177 | } 178 | 179 | // []*Foo -> map[*Foo]struct{} 180 | slice := []*Foo{&f1, &f2, &f1, &f2} 181 | 182 | resultOfSlice := ToSet(slice) 183 | is.True(reflect.TypeOf(resultOfSlice).Kind() == reflect.Map) 184 | 185 | setFromSlice, ok := resultOfSlice.(map[*Foo]struct{}) 186 | is.True(ok) 187 | is.True(len(setFromSlice) == 2) 188 | 189 | for k, v := range setFromSlice { 190 | is.True(reflect.TypeOf(v).Size() == 0) 191 | is.True(k == &f1 || k == &f2) 192 | } 193 | } 194 | 195 | func TestChunk(t *testing.T) { 196 | is := assert.New(t) 197 | 198 | results := Chunk([]int{0, 1, 2, 3, 4}, 2).([][]int) 199 | 200 | is.Len(results, 3) 201 | is.Len(results[0], 2) 202 | is.Len(results[1], 2) 203 | is.Len(results[2], 1) 204 | 205 | is.Len(Chunk([]int{}, 2), 0) 206 | is.Len(Chunk([]int{1}, 2), 1) 207 | is.Len(Chunk([]int{1, 2, 3}, 0), 3) 208 | } 209 | 210 | func TestFlatten(t *testing.T) { 211 | is := assert.New(t) 212 | 213 | is.Equal(Flatten([][][]int{{{1, 2}}, {{3, 4}}}), [][]int{{1, 2}, {3, 4}}) 214 | } 215 | 216 | func TestFlattenDeep(t *testing.T) { 217 | is := assert.New(t) 218 | 219 | is.Equal(FlattenDeep([][][]int{{{1, 2}}, {{3, 4}}}), []int{1, 2, 3, 4}) 220 | } 221 | 222 | func TestShuffle(t *testing.T) { 223 | initial := []int{0, 1, 2, 3, 4} 224 | 225 | results := Shuffle(initial) 226 | 227 | is := assert.New(t) 228 | 229 | is.Len(results, 5) 230 | 231 | for _, entry := range initial { 232 | is.True(Contains(results, entry)) 233 | } 234 | } 235 | 236 | func TestReverse(t *testing.T) { 237 | results := Reverse([]int{0, 1, 2, 3, 4}) 238 | 239 | is := assert.New(t) 240 | 241 | is.Equal(Reverse("abcdefg"), "gfedcba") 242 | is.Len(results, 5) 243 | 244 | is.Equal(results, []int{4, 3, 2, 1, 0}) 245 | } 246 | 247 | func TestUniq(t *testing.T) { 248 | is := assert.New(t) 249 | 250 | results := Uniq([]int{0, 1, 1, 2, 3, 0, 0, 12}) 251 | is.Len(results, 5) 252 | is.Equal(results, []int{0, 1, 2, 3, 12}) 253 | 254 | results = Uniq([]string{"foo", "bar", "foo", "bar", "bar"}) 255 | is.Len(results, 2) 256 | is.Equal(results, []string{"foo", "bar"}) 257 | } 258 | 259 | func TestUniqBy(t *testing.T) { 260 | is := assert.New(t) 261 | 262 | results := UniqBy([]int{0, 1, 1, 2, 3, 0, 0, 12}, func(nbr int) int { 263 | return nbr % 3 264 | }) 265 | fmt.Println(results) 266 | is.Len(results, 3) 267 | is.Equal(results, []int{0, 1, 2}) 268 | 269 | type foobar struct { 270 | foo string 271 | bar string 272 | } 273 | 274 | foobar1 := foobar{ 275 | foo: "foo", 276 | bar: "bar", 277 | } 278 | foobar2 := foobar{ 279 | foo: "foo", 280 | bar: "baz", 281 | } 282 | foobar3 := foobar{ 283 | foo: "foo", 284 | bar: "bar", 285 | } 286 | 287 | results = UniqBy([]foobar{foobar1, foobar2, foobar3}, func(f foobar) string { 288 | return f.foo + f.bar 289 | }) 290 | is.Len(results, 2) 291 | is.Equal(results, []foobar{foobar1, foobar2}) 292 | } 293 | 294 | func TestConvertSlice(t *testing.T) { 295 | instances := []*Foo{foo, foo2} 296 | 297 | var raw []Model 298 | 299 | ConvertSlice(instances, &raw) 300 | 301 | is := assert.New(t) 302 | 303 | is.Len(raw, len(instances)) 304 | } 305 | 306 | func TestDrop(t *testing.T) { 307 | results := Drop([]int{0, 1, 1, 2, 3, 0, 0, 12}, 3) 308 | 309 | is := assert.New(t) 310 | 311 | is.Len(results, 5) 312 | 313 | is.Equal([]int{2, 3, 0, 0, 12}, results) 314 | } 315 | 316 | func TestPrune(t *testing.T) { 317 | testCases := []struct { 318 | OriginalFoo *Foo 319 | Paths []string 320 | ExpectedFoo *Foo 321 | }{ 322 | { 323 | foo, 324 | []string{"FirstName"}, 325 | &Foo{ 326 | FirstName: foo.FirstName, 327 | }, 328 | }, 329 | { 330 | foo, 331 | []string{"FirstName", "ID"}, 332 | &Foo{ 333 | FirstName: foo.FirstName, 334 | ID: foo.ID, 335 | }, 336 | }, 337 | { 338 | foo, 339 | []string{"EmptyValue.Int64"}, 340 | &Foo{ 341 | EmptyValue: sql.NullInt64{ 342 | Int64: foo.EmptyValue.Int64, 343 | }, 344 | }, 345 | }, 346 | { 347 | foo, 348 | []string{"FirstName", "ID", "EmptyValue.Int64"}, 349 | &Foo{ 350 | FirstName: foo.FirstName, 351 | ID: foo.ID, 352 | EmptyValue: sql.NullInt64{ 353 | Int64: foo.EmptyValue.Int64, 354 | }, 355 | }, 356 | }, 357 | { 358 | foo, 359 | []string{"FirstName", "ID", "EmptyValue.Int64"}, 360 | &Foo{ 361 | FirstName: foo.FirstName, 362 | ID: foo.ID, 363 | EmptyValue: sql.NullInt64{ 364 | Int64: foo.EmptyValue.Int64, 365 | }, 366 | }, 367 | }, 368 | { 369 | foo, 370 | []string{"FirstName", "ID", "Bar"}, 371 | &Foo{ 372 | FirstName: foo.FirstName, 373 | ID: foo.ID, 374 | Bar: foo.Bar, 375 | }, 376 | }, 377 | { 378 | foo, 379 | []string{"Bar", "Bars"}, 380 | &Foo{ 381 | Bar: foo.Bar, 382 | Bars: foo.Bars, 383 | }, 384 | }, 385 | { 386 | foo, 387 | []string{"FirstName", "Bars.Name"}, 388 | &Foo{ 389 | FirstName: foo.FirstName, 390 | Bars: []*Bar{ 391 | {Name: bar.Name}, 392 | {Name: bar.Name}, 393 | }, 394 | }, 395 | }, 396 | { 397 | foo, 398 | []string{"Bars.Name", "Bars.Bars.Name"}, 399 | &Foo{ 400 | Bars: []*Bar{ 401 | {Name: bar.Name, Bars: []*Bar{{Name: "Level1-1"}, {Name: "Level1-2"}}}, 402 | {Name: bar.Name, Bars: []*Bar{{Name: "Level1-1"}, {Name: "Level1-2"}}}, 403 | }, 404 | }, 405 | }, 406 | { 407 | foo, 408 | []string{"BarInterface", "BarPointer"}, 409 | &Foo{ 410 | BarInterface: bar, 411 | BarPointer: &bar, 412 | }, 413 | }, 414 | } 415 | 416 | // pass to prune by pointer to struct 417 | for idx, tc := range testCases { 418 | t.Run(fmt.Sprintf("Prune pointer test case #%v", idx), func(t *testing.T) { 419 | is := assert.New(t) 420 | res, err := Prune(tc.OriginalFoo, tc.Paths) 421 | require.NoError(t, err) 422 | 423 | fooPrune := res.(*Foo) 424 | is.Equal(tc.ExpectedFoo, fooPrune) 425 | }) 426 | } 427 | 428 | // pass to prune by struct directly 429 | for idx, tc := range testCases { 430 | t.Run(fmt.Sprintf("Prune non pointer test case #%v", idx), func(t *testing.T) { 431 | is := assert.New(t) 432 | fooNonPtr := *tc.OriginalFoo 433 | res, err := Prune(fooNonPtr, tc.Paths) 434 | require.NoError(t, err) 435 | 436 | fooPrune := res.(Foo) 437 | is.Equal(*tc.ExpectedFoo, fooPrune) 438 | }) 439 | } 440 | 441 | // test PruneByTag 442 | TagTestCases := []struct { 443 | OriginalFoo *Foo 444 | Paths []string 445 | ExpectedFoo *Foo 446 | Tag string 447 | }{ 448 | { 449 | foo, 450 | []string{"tag 1", "tag 4.BarName"}, 451 | &Foo{ 452 | FirstName: foo.FirstName, 453 | Bar: &Bar{ 454 | Name: bar.Name, 455 | }, 456 | }, 457 | "tag_name", 458 | }, 459 | } 460 | 461 | for idx, tc := range TagTestCases { 462 | t.Run(fmt.Sprintf("PruneByTag test case #%v", idx), func(t *testing.T) { 463 | is := assert.New(t) 464 | fooNonPtr := *tc.OriginalFoo 465 | res, err := PruneByTag(fooNonPtr, tc.Paths, tc.Tag) 466 | require.NoError(t, err) 467 | 468 | fooPrune := res.(Foo) 469 | is.Equal(*tc.ExpectedFoo, fooPrune) 470 | }) 471 | } 472 | 473 | t.Run("Bar Slice", func(t *testing.T) { 474 | barSlice := []*Bar{bar, bar} 475 | barSlicePruned, err := pruneByTag(barSlice, []string{"Name"}, nil /*tag*/) 476 | require.NoError(t, err) 477 | assert.Equal(t, []*Bar{{Name: bar.Name}, {Name: bar.Name}}, barSlicePruned) 478 | }) 479 | 480 | t.Run("Bar Array", func(t *testing.T) { 481 | barArr := [2]*Bar{bar, bar} 482 | barArrPruned, err := pruneByTag(barArr, []string{"Name"}, nil /*tag*/) 483 | require.NoError(t, err) 484 | assert.Equal(t, [2]*Bar{{Name: bar.Name}, {Name: bar.Name}}, barArrPruned) 485 | }) 486 | 487 | // test values are copied and not referenced in return result 488 | // NOTE: pointers at the end of path are referenced. Maybe we need to make a copy 489 | t.Run("Copy Value Str", func(t *testing.T) { 490 | is := assert.New(t) 491 | fooTest := &Foo{ 492 | Bar: &Bar{ 493 | Name: "bar", 494 | }, 495 | } 496 | res, err := pruneByTag(fooTest, []string{"Bar.Name"}, nil) 497 | require.NoError(t, err) 498 | fooTestPruned := res.(*Foo) 499 | is.Equal(fooTest, fooTestPruned) 500 | 501 | // change pruned 502 | fooTestPruned.Bar.Name = "changed bar" 503 | // check original is unchanged 504 | is.Equal(fooTest.Bar.Name, "bar") 505 | }) 506 | 507 | // error cases 508 | errCases := []struct { 509 | InputFoo *Foo 510 | Paths []string 511 | TagName *string 512 | }{ 513 | { 514 | foo, 515 | []string{"NotExist"}, 516 | nil, 517 | }, 518 | { 519 | foo, 520 | []string{"FirstName.NotExist", "LastName"}, 521 | nil, 522 | }, 523 | { 524 | foo, 525 | []string{"LastName", "FirstName.NotExist"}, 526 | nil, 527 | }, 528 | { 529 | foo, 530 | []string{"LastName", "Bars.NotExist"}, 531 | nil, 532 | }, 533 | // tags 534 | { 535 | foo, 536 | []string{"tag 999"}, 537 | &[]string{"tag_name"}[0], 538 | }, 539 | { 540 | foo, 541 | []string{"tag 1.NotExist"}, 542 | &[]string{"tag_name"}[0], 543 | }, 544 | { 545 | foo, 546 | []string{"tag 4.NotExist"}, 547 | &[]string{"tag_name"}[0], 548 | }, 549 | { 550 | foo, 551 | []string{"FirstName"}, 552 | &[]string{"tag_name_not_exist"}[0], 553 | }, 554 | } 555 | 556 | for idx, errTC := range errCases { 557 | t.Run(fmt.Sprintf("error test case #%v", idx), func(t *testing.T) { 558 | _, err := pruneByTag(errTC.InputFoo, errTC.Paths, errTC.TagName) 559 | assert.Error(t, err) 560 | }) 561 | } 562 | } 563 | 564 | func ExamplePrune() { 565 | type ExampleFoo struct { 566 | ExampleFooPtr *ExampleFoo `json:"example_foo_ptr"` 567 | Name string `json:"name"` 568 | Number int `json:"number"` 569 | } 570 | 571 | exampleFoo := ExampleFoo{ 572 | ExampleFooPtr: &ExampleFoo{ 573 | Name: "ExampleFooPtr", 574 | Number: 2, 575 | }, 576 | Name: "ExampleFoo", 577 | Number: 1, 578 | } 579 | 580 | // prune using struct field name 581 | res, _ := Prune(exampleFoo, []string{"ExampleFooPtr.Name", "Number"}) 582 | prunedFoo := res.(ExampleFoo) 583 | fmt.Println(prunedFoo.ExampleFooPtr.Name) 584 | fmt.Println(prunedFoo.ExampleFooPtr.Number) 585 | fmt.Println(prunedFoo.Name) 586 | fmt.Println(prunedFoo.Number) 587 | 588 | // prune using struct json tag 589 | res2, _ := PruneByTag(exampleFoo, []string{"example_foo_ptr.name", "number"}, "json") 590 | prunedByTagFoo := res2.(ExampleFoo) 591 | fmt.Println(prunedByTagFoo.ExampleFooPtr.Name) 592 | fmt.Println(prunedByTagFoo.ExampleFooPtr.Number) 593 | fmt.Println(prunedByTagFoo.Name) 594 | fmt.Println(prunedByTagFoo.Number) 595 | // output: 596 | // ExampleFooPtr 597 | // 0 598 | // 599 | // 1 600 | // ExampleFooPtr 601 | // 0 602 | // 603 | // 1 604 | } 605 | -------------------------------------------------------------------------------- /typesafe_test.go: -------------------------------------------------------------------------------- 1 | package funk 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestContainsBool(t *testing.T) { 10 | is := assert.New(t) 11 | 12 | is.True(ContainsBool([]bool{true, false}, true)) 13 | is.False(ContainsBool([]bool{true}, false)) 14 | } 15 | 16 | func TestContainsInt(t *testing.T) { 17 | is := assert.New(t) 18 | 19 | is.True(ContainsInt([]int{1, 2, 3, 4}, 4)) 20 | is.False(ContainsInt([]int{1, 2, 3, 4}, 5)) 21 | 22 | is.True(ContainsInt32([]int32{1, 2, 3, 4}, 4)) 23 | is.False(ContainsInt32([]int32{1, 2, 3, 4}, 5)) 24 | 25 | is.True(ContainsInt64([]int64{1, 2, 3, 4}, 4)) 26 | is.False(ContainsInt64([]int64{1, 2, 3, 4}, 5)) 27 | 28 | is.True(ContainsUInt([]uint{1, 2, 3, 4}, 4)) 29 | is.False(ContainsUInt([]uint{1, 2, 3, 4}, 5)) 30 | 31 | is.True(ContainsUInt32([]uint32{1, 2, 3, 4}, 4)) 32 | is.False(ContainsUInt32([]uint32{1, 2, 3, 4}, 5)) 33 | 34 | is.True(ContainsUInt64([]uint64{1, 2, 3, 4}, 4)) 35 | is.False(ContainsUInt64([]uint64{1, 2, 3, 4}, 5)) 36 | } 37 | 38 | func TestContainsString(t *testing.T) { 39 | is := assert.New(t) 40 | 41 | is.True(ContainsString([]string{"flo", "gilles"}, "flo")) 42 | is.False(ContainsString([]string{"flo", "gilles"}, "alex")) 43 | } 44 | 45 | func TestFilterBool(t *testing.T) { 46 | is := assert.New(t) 47 | 48 | r := FilterBool([]bool{true, true, false, true}, func(x bool) bool { 49 | return x == true 50 | }) 51 | 52 | is.Equal(r, []bool{true, true, true}) 53 | } 54 | 55 | func TestFilterString(t *testing.T) { 56 | is := assert.New(t) 57 | 58 | r := FilterString([]string{"a", "b", "c", "d"}, func(x string) bool { 59 | return x >= "c" 60 | }) 61 | 62 | is.Equal(r, []string{"c", "d"}) 63 | } 64 | 65 | func TestFilterInt(t *testing.T) { 66 | is := assert.New(t) 67 | 68 | r := FilterInt([]int{1, 2, 3, 4}, func(x int) bool { 69 | return x%2 == 0 70 | }) 71 | 72 | is.Equal(r, []int{2, 4}) 73 | } 74 | 75 | func TestFilterInt32(t *testing.T) { 76 | is := assert.New(t) 77 | 78 | r := FilterInt32([]int32{1, 2, 3, 4}, func(x int32) bool { 79 | return x%2 == 0 80 | }) 81 | 82 | is.Equal(r, []int32{2, 4}) 83 | } 84 | 85 | func TestFilterInt64(t *testing.T) { 86 | is := assert.New(t) 87 | 88 | r := FilterInt64([]int64{1, 2, 3, 4}, func(x int64) bool { 89 | return x%2 == 0 90 | }) 91 | 92 | is.Equal(r, []int64{2, 4}) 93 | } 94 | 95 | func TestFilterUInt(t *testing.T) { 96 | is := assert.New(t) 97 | 98 | r := FilterUInt([]uint{1, 2, 3, 4}, func(x uint) bool { 99 | return x%2 == 0 100 | }) 101 | 102 | is.Equal(r, []uint{2, 4}) 103 | } 104 | 105 | func TestFilterUInt32(t *testing.T) { 106 | is := assert.New(t) 107 | 108 | r := FilterUInt32([]uint32{1, 2, 3, 4}, func(x uint32) bool { 109 | return x%2 == 0 110 | }) 111 | 112 | is.Equal(r, []uint32{2, 4}) 113 | } 114 | 115 | func TestFilterUInt64(t *testing.T) { 116 | is := assert.New(t) 117 | 118 | r := FilterUInt64([]uint64{1, 2, 3, 4}, func(x uint64) bool { 119 | return x%2 == 0 120 | }) 121 | 122 | is.Equal(r, []uint64{2, 4}) 123 | } 124 | 125 | func TestFilterFloat64(t *testing.T) { 126 | is := assert.New(t) 127 | 128 | r := FilterFloat64([]float64{1.0, 2.0, 3.0, 4.0}, func(x float64) bool { 129 | return int(x)%2 == 0 130 | }) 131 | 132 | is.Equal(r, []float64{2.0, 4.0}) 133 | } 134 | 135 | func TestFilterFloat32(t *testing.T) { 136 | is := assert.New(t) 137 | 138 | r := FilterFloat32([]float32{1.0, 2.0, 3.0, 4.0}, func(x float32) bool { 139 | return int(x)%2 == 0 140 | }) 141 | 142 | is.Equal(r, []float32{2.0, 4.0}) 143 | } 144 | 145 | func TestContainsFloat(t *testing.T) { 146 | is := assert.New(t) 147 | 148 | is.True(ContainsFloat64([]float64{0.1, 0.2}, 0.1)) 149 | is.False(ContainsFloat64([]float64{0.1, 0.2}, 0.3)) 150 | 151 | is.True(ContainsFloat32([]float32{0.1, 0.2}, 0.1)) 152 | is.False(ContainsFloat32([]float32{0.1, 0.2}, 0.3)) 153 | } 154 | 155 | func TestSumNumeral(t *testing.T) { 156 | is := assert.New(t) 157 | 158 | is.Equal(SumInt([]int{1, 2, 3}), 6) 159 | is.Equal(SumInt64([]int64{1, 2, 3}), int64(6)) 160 | 161 | is.Equal(SumUInt([]uint{1, 2, 3}), uint(6)) 162 | is.Equal(SumUInt64([]uint64{1, 2, 3}), uint64(6)) 163 | 164 | is.Equal(SumFloat32([]float32{0.1, 0.2, 0.1}), float32(0.4)) 165 | is.Equal(SumFloat64([]float64{0.1, 0.2, 0.1}), float64(0.4)) 166 | } 167 | 168 | func TestTypesafeReverse(t *testing.T) { 169 | is := assert.New(t) 170 | 171 | is.Equal(ReverseBools([]bool{true, false, false}), []bool{false, false, true}) 172 | is.Equal(ReverseString("abcdefg"), "gfedcba") 173 | is.Equal(ReverseInt([]int{1, 2, 3, 4}), []int{4, 3, 2, 1}) 174 | is.Equal(ReverseInt64([]int64{1, 2, 3, 4}), []int64{4, 3, 2, 1}) 175 | is.Equal(ReverseUInt([]uint{1, 2, 3, 4}), []uint{4, 3, 2, 1}) 176 | is.Equal(ReverseUInt64([]uint64{1, 2, 3, 4}), []uint64{4, 3, 2, 1}) 177 | is.Equal(ReverseStrings([]string{"flo", "gilles"}), []string{"gilles", "flo"}) 178 | is.Equal(ReverseFloat64([]float64{0.1, 0.2, 0.3}), []float64{0.3, 0.2, 0.1}) 179 | is.Equal(ReverseFloat32([]float32{0.1, 0.2, 0.3}), []float32{0.3, 0.2, 0.1}) 180 | } 181 | 182 | func TestTypesafeIndexOf(t *testing.T) { 183 | is := assert.New(t) 184 | 185 | is.Equal(IndexOfBool([]bool{true, false}, false), 1) 186 | is.Equal(IndexOfBool([]bool{true}, false), -1) 187 | 188 | is.Equal(IndexOfString([]string{"foo", "bar"}, "bar"), 1) 189 | is.Equal(IndexOfString([]string{"foo", "bar"}, "flo"), -1) 190 | 191 | is.Equal(IndexOfInt([]int{0, 1, 2}, 1), 1) 192 | is.Equal(IndexOfInt([]int{0, 1, 2}, 3), -1) 193 | 194 | is.Equal(IndexOfInt64([]int64{0, 1, 2}, 1), 1) 195 | is.Equal(IndexOfInt64([]int64{0, 1, 2}, 3), -1) 196 | 197 | is.Equal(IndexOfUInt64([]uint64{0, 1, 2}, 1), 1) 198 | is.Equal(IndexOfUInt64([]uint64{0, 1, 2}, 3), -1) 199 | 200 | is.Equal(IndexOfFloat64([]float64{0.1, 0.2, 0.3}, 0.2), 1) 201 | is.Equal(IndexOfFloat64([]float64{0.1, 0.2, 0.3}, 0.4), -1) 202 | } 203 | 204 | func TestTypesafeLastIndexOf(t *testing.T) { 205 | is := assert.New(t) 206 | 207 | is.Equal(LastIndexOfBool([]bool{true, true, false, true}, true), 3) 208 | is.Equal(LastIndexOfString([]string{"foo", "bar", "bar"}, "bar"), 2) 209 | is.Equal(LastIndexOfInt([]int{1, 2, 2, 3}, 2), 2) 210 | is.Equal(LastIndexOfInt64([]int64{1, 2, 2, 3}, 4), -1) 211 | is.Equal(LastIndexOfUInt([]uint{1, 2, 2, 3}, 2), 2) 212 | is.Equal(LastIndexOfUInt64([]uint64{1, 2, 2, 3}, 4), -1) 213 | } 214 | 215 | func TestTypesafeUniq(t *testing.T) { 216 | is := assert.New(t) 217 | 218 | is.Equal(UniqBool([]bool{true, false, false, true, false}), []bool{true, false}) 219 | is.Equal(UniqInt64([]int64{0, 1, 1, 2, 3, 0, 0, 12}), []int64{0, 1, 2, 3, 12}) 220 | is.Equal(UniqInt([]int{0, 1, 1, 2, 3, 0, 0, 12}), []int{0, 1, 2, 3, 12}) 221 | is.Equal(UniqUInt([]uint{0, 1, 1, 2, 3, 0, 0, 12}), []uint{0, 1, 2, 3, 12}) 222 | is.Equal(UniqUInt64([]uint64{0, 1, 1, 2, 3, 0, 0, 12}), []uint64{0, 1, 2, 3, 12}) 223 | is.Equal(UniqFloat64([]float64{0.0, 0.1, 0.1, 0.2, 0.3, 0.0, 0.0, 0.12}), []float64{0.0, 0.1, 0.2, 0.3, 0.12}) 224 | is.Equal(UniqString([]string{"foo", "bar", "foo", "bar"}), []string{"foo", "bar"}) 225 | } 226 | 227 | func TestTypesafeShuffle(t *testing.T) { 228 | is := assert.New(t) 229 | 230 | initial := []int{1, 2, 3, 5} 231 | 232 | results := ShuffleInt(initial) 233 | 234 | is.Len(results, 4) 235 | 236 | for _, entry := range initial { 237 | is.True(ContainsInt(results, entry)) 238 | } 239 | } 240 | 241 | func TestDropBool(t *testing.T) { 242 | results := DropBool([]bool{true, false, false, true, true}, 3) 243 | 244 | is := assert.New(t) 245 | 246 | is.Len(results, 2) 247 | 248 | is.Equal([]bool{true, true}, results) 249 | } 250 | 251 | func TestDropString(t *testing.T) { 252 | results := DropString([]string{"the", "quick", "brown", "fox", "jumps", "..."}, 3) 253 | 254 | is := assert.New(t) 255 | 256 | is.Len(results, 3) 257 | 258 | is.Equal([]string{"fox", "jumps", "..."}, results) 259 | } 260 | 261 | func TestDropInt(t *testing.T) { 262 | results := DropInt([]int{0, 0, 0, 0}, 3) 263 | 264 | is := assert.New(t) 265 | 266 | is.Len(results, 1) 267 | 268 | is.Equal([]int{0}, results) 269 | } 270 | 271 | func TestDropInt32(t *testing.T) { 272 | results := DropInt32([]int32{1, 2, 3, 4}, 3) 273 | 274 | is := assert.New(t) 275 | 276 | is.Len(results, 1) 277 | 278 | is.Equal([]int32{4}, results) 279 | } 280 | 281 | func TestDropInt64(t *testing.T) { 282 | results := DropInt64([]int64{1, 2, 3, 4}, 3) 283 | 284 | is := assert.New(t) 285 | 286 | is.Len(results, 1) 287 | 288 | is.Equal([]int64{4}, results) 289 | } 290 | 291 | func TestDropUInt(t *testing.T) { 292 | results := DropUInt([]uint{0, 0, 0, 0}, 3) 293 | 294 | is := assert.New(t) 295 | 296 | is.Len(results, 1) 297 | 298 | is.Equal([]uint{0}, results) 299 | } 300 | 301 | func TestDropUInt32(t *testing.T) { 302 | results := DropUInt32([]uint32{1, 2, 3, 4}, 3) 303 | 304 | is := assert.New(t) 305 | 306 | is.Len(results, 1) 307 | 308 | is.Equal([]uint32{4}, results) 309 | } 310 | 311 | func TestDropUInt64(t *testing.T) { 312 | results := DropUInt64([]uint64{1, 2, 3, 4}, 3) 313 | 314 | is := assert.New(t) 315 | 316 | is.Len(results, 1) 317 | 318 | is.Equal([]uint64{4}, results) 319 | } 320 | 321 | func TestDropFloat32(t *testing.T) { 322 | results := DropFloat32([]float32{1.1, 2.2, 3.3, 4.4}, 3) 323 | 324 | is := assert.New(t) 325 | 326 | is.Len(results, 1) 327 | 328 | is.Equal([]float32{4.4}, results) 329 | } 330 | 331 | func TestDropFloat64(t *testing.T) { 332 | results := DropFloat64([]float64{1.1, 2.2, 3.3, 4.4}, 3) 333 | 334 | is := assert.New(t) 335 | 336 | is.Len(results, 1) 337 | 338 | is.Equal([]float64{4.4}, results) 339 | } 340 | 341 | func TestChunkStrings(t *testing.T) { 342 | is := assert.New(t) 343 | 344 | results := ChunkStrings([]string{"foo", "bar", "foo", "bar", "bar"}, 2) 345 | 346 | is.Len(results, 3) 347 | is.Len(results[0], 2) 348 | is.Len(results[1], 2) 349 | is.Len(results[2], 1) 350 | is.Equal([]string{"foo", "bar"}, results[0]) 351 | is.Equal([]string{"foo", "bar"}, results[1]) 352 | is.Equal([]string{"bar"}, results[2]) 353 | } 354 | -------------------------------------------------------------------------------- /union.go: -------------------------------------------------------------------------------- 1 | package funk 2 | 3 | import ( 4 | "reflect" 5 | ) 6 | 7 | // Union returns the union between two collections. 8 | func Union(collections ...interface{}) interface{} { 9 | // shortcut zero/single argument 10 | if len(collections) == 0 { 11 | return nil 12 | } else if len(collections) == 1 { 13 | return collections[0] 14 | } 15 | 16 | if !IsIteratee(collections[0]) { 17 | panic("Parameter must be a collection") 18 | } 19 | 20 | cType := reflect.TypeOf(collections[0]) 21 | zLen := 0 22 | 23 | for i, x := range collections { 24 | xValue := reflect.ValueOf(x) 25 | xType := xValue.Type() 26 | if i > 0 && NotEqual(cType, xType) { 27 | panic("Parameters must have the same type") 28 | } 29 | 30 | zLen += xValue.Len() 31 | } 32 | 33 | if cType.Kind() == reflect.Map { 34 | zType := reflect.MapOf(cType.Key(), cType.Elem()) 35 | zMap := reflect.MakeMap(zType) 36 | 37 | for _, x := range collections { 38 | xIter := reflect.ValueOf(x).MapRange() 39 | for xIter.Next() { 40 | zMap.SetMapIndex(xIter.Key(), xIter.Value()) 41 | } 42 | } 43 | 44 | return zMap.Interface() 45 | } else { 46 | zType := reflect.SliceOf(cType.Elem()) 47 | zSlice := reflect.MakeSlice(zType, 0, 0) 48 | 49 | for _, x := range collections { 50 | xValue := reflect.ValueOf(x) 51 | zSlice = reflect.AppendSlice(zSlice, xValue) 52 | } 53 | 54 | return zSlice.Interface() 55 | } 56 | } 57 | 58 | // UnionStringMap returns the union between multiple string maps 59 | func UnionStringMap(x ...map[string]string) map[string]string { 60 | zMap := map[string]string{} 61 | for _, xMap := range x { 62 | for k, v := range xMap { 63 | zMap[k] = v 64 | } 65 | } 66 | return zMap 67 | } 68 | -------------------------------------------------------------------------------- /union_test.go: -------------------------------------------------------------------------------- 1 | package funk 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestUnion(t *testing.T) { 10 | is := assert.New(t) 11 | 12 | r := Union([]int{1, 2, 3, 4}, []int{2, 4, 6}) 13 | is.Equal(r, []int{1, 2, 3, 4, 2, 4, 6}) 14 | 15 | r = Union(map[int]int{1: 1, 2: 2}, map[int]int{1: 0, 3: 3}) 16 | is.Equal(r, map[int]int{1: 0, 2: 2, 3: 3}) 17 | } 18 | 19 | func TestUnionShortcut(t *testing.T) { 20 | is := assert.New(t) 21 | 22 | r := Union(nil) 23 | is.Nil(r) 24 | 25 | r = Union([]int{1, 2}) 26 | is.Equal(r, []int{1, 2}) 27 | } 28 | 29 | func TestUnionStringMap(t *testing.T) { 30 | is := assert.New(t) 31 | 32 | r := Union(map[string]string{"a": "a", "b": "b"}, map[string]string{"a": "z", "z": "a"}, map[string]string{"z": "z"}) 33 | is.Equal(r, map[string]string{"a": "z", "b": "b", "z": "z"}) 34 | } 35 | -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | package funk 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | func equal(expectedOrPredicate interface{}, optionalIsMap ...bool) func(keyValueIfMap, actualValue reflect.Value) bool { 9 | isMap := append(optionalIsMap, false)[0] 10 | 11 | if IsFunction(expectedOrPredicate) { 12 | inTypes := []reflect.Type{nil}; if isMap { 13 | inTypes = append(inTypes, nil) 14 | } 15 | 16 | if !IsPredicate(expectedOrPredicate, inTypes...) { 17 | panic(fmt.Sprintf("Predicate function must have %d parameter and must return boolean", len(inTypes))) 18 | } 19 | 20 | predicateValue := reflect.ValueOf(expectedOrPredicate) 21 | 22 | return func(keyValueIfMap, actualValue reflect.Value) bool { 23 | 24 | if isMap && !keyValueIfMap.Type().ConvertibleTo(predicateValue.Type().In(0)) { 25 | panic("Given key is not compatible with type of parameter for the predicate.") 26 | } 27 | 28 | if (isMap && !actualValue.Type().ConvertibleTo(predicateValue.Type().In(1))) || 29 | (!isMap && !actualValue.Type().ConvertibleTo(predicateValue.Type().In(0))) { 30 | panic("Given value is not compatible with type of parameter for the predicate.") 31 | } 32 | 33 | args := []reflect.Value{actualValue} 34 | if isMap { 35 | args = append([]reflect.Value{keyValueIfMap}, args...) 36 | } 37 | 38 | return predicateValue.Call(args)[0].Bool() 39 | } 40 | } 41 | 42 | expected := expectedOrPredicate 43 | 44 | return func(keyValueIfMap, actualValue reflect.Value) bool { 45 | if isMap { 46 | actualValue = keyValueIfMap 47 | } 48 | 49 | if expected == nil || actualValue.IsZero() { 50 | return actualValue.Interface() == expected 51 | } 52 | 53 | return reflect.DeepEqual(actualValue.Interface(), expected) 54 | } 55 | } 56 | 57 | func sliceElem(rtype reflect.Type) reflect.Type { 58 | for { 59 | if rtype.Kind() != reflect.Slice && rtype.Kind() != reflect.Array { 60 | return rtype 61 | } 62 | 63 | rtype = rtype.Elem() 64 | } 65 | } 66 | 67 | func redirectValue(value reflect.Value) reflect.Value { 68 | for { 69 | if !value.IsValid() || (value.Kind() != reflect.Ptr && value.Kind() != reflect.Interface) { 70 | return value 71 | } 72 | 73 | res := value.Elem() 74 | 75 | // Test for a circular type. 76 | if res.Kind() == reflect.Ptr && value.Kind() == reflect.Ptr && value.Pointer() == res.Pointer() { 77 | return value 78 | } 79 | 80 | if !res.IsValid() && value.Kind() == reflect.Ptr { 81 | return reflect.Zero(value.Type().Elem()) 82 | } 83 | 84 | value = res 85 | } 86 | } 87 | 88 | func makeSlice(value reflect.Value, values ...int) reflect.Value { 89 | sliceType := sliceElem(value.Type()) 90 | 91 | size := value.Len() 92 | cap := size 93 | 94 | if len(values) > 0 { 95 | size = values[0] 96 | } 97 | 98 | if len(values) > 1 { 99 | cap = values[1] 100 | } 101 | 102 | return reflect.MakeSlice(reflect.SliceOf(sliceType), size, cap) 103 | } 104 | -------------------------------------------------------------------------------- /utils_test.go: -------------------------------------------------------------------------------- 1 | package funk 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestRedirectValue(t *testing.T) { 11 | is := assert.New(t) 12 | 13 | val := 1 14 | 15 | is.Equal(redirectValue(reflect.ValueOf(&val)).Interface(), 1) 16 | } 17 | -------------------------------------------------------------------------------- /without.go: -------------------------------------------------------------------------------- 1 | package funk 2 | 3 | import "reflect" 4 | 5 | // Without creates an array excluding all given values. 6 | func Without(in interface{}, values ...interface{}) interface{} { 7 | if !IsCollection(in) { 8 | panic("First parameter must be a collection") 9 | } 10 | 11 | inValue := reflect.ValueOf(in) 12 | for _, value := range values { 13 | if NotEqual(inValue.Type().Elem(), reflect.TypeOf(value)) { 14 | panic("Values must have the same type") 15 | } 16 | } 17 | 18 | return LeftJoin(inValue, reflect.ValueOf(values)).Interface() 19 | } 20 | -------------------------------------------------------------------------------- /without_test.go: -------------------------------------------------------------------------------- 1 | package funk 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestWithout(t *testing.T) { 11 | testCases := []struct { 12 | Arr interface{} 13 | Values []interface{} 14 | Expect interface{} 15 | }{ 16 | {[]string{"foo", "bar"}, []interface{}{"bar"}, []string{"foo"}}, 17 | {[]int{0, 1, 2, 3, 4}, []interface{}{3, 4}, []int{0, 1, 2}}, 18 | {[]*Foo{f, b}, []interface{}{b, c}, []*Foo{f}}, 19 | } 20 | 21 | for idx, tt := range testCases { 22 | t.Run(fmt.Sprintf("test case #%d", idx+1), func(t *testing.T) { 23 | is := assert.New(t) 24 | 25 | actual := Without(tt.Arr, tt.Values...) 26 | is.Equal(tt.Expect, actual) 27 | }) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /zip.go: -------------------------------------------------------------------------------- 1 | package funk 2 | 3 | import ( 4 | "reflect" 5 | ) 6 | 7 | // Tuple is the return type of Zip 8 | type Tuple struct { 9 | Element1 interface{} 10 | Element2 interface{} 11 | } 12 | 13 | // Zip returns a list of tuples, where the i-th tuple contains the i-th element 14 | // from each of the input iterables. The returned list is truncated in length 15 | // to the length of the shortest input iterable. 16 | func Zip(slice1 interface{}, slice2 interface{}) []Tuple { 17 | if !IsCollection(slice1) || !IsCollection(slice2) { 18 | panic("First parameter must be a collection") 19 | } 20 | 21 | var ( 22 | minLength int 23 | inValue1 = reflect.ValueOf(slice1) 24 | inValue2 = reflect.ValueOf(slice2) 25 | result = []Tuple{} 26 | length1 = inValue1.Len() 27 | length2 = inValue2.Len() 28 | ) 29 | 30 | if length1 <= length2 { 31 | minLength = length1 32 | } else { 33 | minLength = length2 34 | } 35 | 36 | for i := 0; i < minLength; i++ { 37 | newTuple := Tuple{ 38 | Element1: inValue1.Index(i).Interface(), 39 | Element2: inValue2.Index(i).Interface(), 40 | } 41 | result = append(result, newTuple) 42 | } 43 | return result 44 | } 45 | -------------------------------------------------------------------------------- /zip_test.go: -------------------------------------------------------------------------------- 1 | package funk 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestZipEmptyResult(t *testing.T) { 10 | map1 := map[string]int{"a": 1, "b": 2} 11 | array1 := []int{21, 22, 23} 12 | emptySlice := []int{} 13 | 14 | t.Run("NonSliceOrArray", func(t *testing.T) { 15 | assert.Panics(t, func() { Zip(map1, array1) }, "It should panic") 16 | }) 17 | 18 | t.Run("ZerosSized", func(t *testing.T) { 19 | expected := []Tuple{} 20 | result := Zip(emptySlice, array1) 21 | assert.Equal(t, result, expected) 22 | }) 23 | } 24 | 25 | func zipIntsAndAssert(t *testing.T, data1, data2 interface{}) { 26 | t.Run("FirstOneShorter", func(t *testing.T) { 27 | expected := []Tuple{ 28 | {Element1: 11, Element2: 21}, 29 | {Element1: 12, Element2: 22}, 30 | {Element1: 13, Element2: 23}, 31 | } 32 | result := Zip(data1, data2) 33 | assert.Equal(t, result, expected) 34 | }) 35 | 36 | t.Run("SecondOneShorter", func(t *testing.T) { 37 | expected := []Tuple{ 38 | {Element1: 21, Element2: 11}, 39 | {Element1: 22, Element2: 12}, 40 | {Element1: 23, Element2: 13}, 41 | } 42 | result := Zip(data2, data1) 43 | assert.Equal(t, result, expected) 44 | }) 45 | } 46 | 47 | func TestZipSlices(t *testing.T) { 48 | slice1 := []int{11, 12, 13} 49 | slice2 := []int{21, 22, 23, 24, 25} 50 | zipIntsAndAssert(t, slice1, slice2) 51 | } 52 | 53 | func TestZipArrays(t *testing.T) { 54 | array1 := [...]int{11, 12, 13} 55 | array2 := [...]int{21, 22, 23, 24, 25} 56 | zipIntsAndAssert(t, array1, array2) 57 | } 58 | 59 | func TestZipStructs(t *testing.T) { 60 | type struct1 struct { 61 | Member1 uint16 62 | Member2 string 63 | } 64 | type struct2 struct { 65 | Member3 bool 66 | } 67 | type struct3 struct { 68 | Member4 int 69 | Member5 struct2 70 | } 71 | 72 | slice1 := []struct1{ 73 | { 74 | Member1: 11, 75 | Member2: "a", 76 | }, 77 | { 78 | Member1: 12, 79 | Member2: "b", 80 | }, 81 | { 82 | Member1: 13, 83 | Member2: "c", 84 | }, 85 | } 86 | slice2 := []struct3{ 87 | { 88 | Member4: 21, 89 | Member5: struct2{ 90 | Member3: false, 91 | }, 92 | }, 93 | { 94 | Member4: 22, 95 | Member5: struct2{ 96 | Member3: true, 97 | }, 98 | }, 99 | } 100 | 101 | expected := []Tuple{ 102 | { 103 | Element1: struct1{ 104 | Member1: 11, 105 | Member2: "a", 106 | }, 107 | Element2: struct3{ 108 | Member4: 21, 109 | Member5: struct2{ 110 | Member3: false, 111 | }, 112 | }, 113 | }, 114 | { 115 | Element1: struct1{ 116 | Member1: 12, 117 | Member2: "b", 118 | }, 119 | Element2: struct3{ 120 | Member4: 22, 121 | Member5: struct2{ 122 | Member3: true, 123 | }, 124 | }, 125 | }, 126 | } 127 | 128 | result := Zip(slice1, slice2) 129 | assert.Equal(t, expected, result) 130 | } 131 | --------------------------------------------------------------------------------