├── go.mod ├── .gitignore ├── LICENSE ├── README.md ├── slice.go └── slice_test.go /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/luraim/fun 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fun 2 | [![GoDoc](https://godoc.org/github.com/luraim/fun?status.svg)](https://godoc.org/github.com/luraim/fun) 3 | 4 | ### Simple generic utility functions to reduce golang boilerplate 5 | - Inspired by Kotlin and Rust collection functions 6 | - Supplement to the generic functions in golang.org/x/exp/slices and golang.org/x/exp/maps 7 | - Note: The Go compiler does not currently inline generic callback functions. So please use your judgement while using functions from this library that involve callbacks. Use them when the expressiveness is worth any performance degration compared to handcoded *for loop* boilerplate. 8 | 9 | ## List of functions 10 | - [fun](#fun) 11 | - [Simple generic utility functions to reduce golang boilerplate](#simple-generic-utility-functions-to-reduce-golang-boilerplate) 12 | - [List of functions](#list-of-functions) 13 | - [All](#all) 14 | - [Any](#any) 15 | - [AppendToGroup](#appendtogroup) 16 | - [Associate](#associate) 17 | - [Chunked](#chunked) 18 | - [ChunkedBy](#chunkedby) 19 | - [Distinct](#distinct) 20 | - [DistinctBy](#distinctby) 21 | - [Drop](#drop) 22 | - [DropLast](#droplast) 23 | - [DropWhile](#dropwhile) 24 | - [DropLastWhile](#droplastwhile) 25 | - [Filter](#filter) 26 | - [FilterIndexed](#filterindexed) 27 | - [FilterMap](#filtermap) 28 | - [FlatMap](#flatmap) 29 | - [FlatMapIndexed](#flatmapindexed) 30 | - [Fold](#fold) 31 | - [FoldIndexed](#foldindexed) 32 | - [FoldItems](#folditems) 33 | - [GetOrInsert](#getorinsert) 34 | - [GroupBy](#groupby) 35 | - [Items](#items) 36 | - [Map](#map) 37 | - [MapIndexed](#mapindexed) 38 | - [Partition](#partition) 39 | - [Reduce](#reduce) 40 | - [ReduceIndexed](#reduceindexed) 41 | - [Reverse](#reverse) 42 | - [Reversed](#reversed) 43 | - [Take](#take) 44 | - [TakeLast](#takelast) 45 | - [TakeWhile](#takewhile) 46 | - [TakeLastWhile](#takelastwhile) 47 | - [TransformMap](#transformmap) 48 | - [Unzip](#unzip) 49 | - [Windowed](#windowed) 50 | - [Zip](#zip) 51 | 52 | ### All 53 | - Returns true if all elements return true for given predicate 54 | ```go 55 | All([]int{1, 2, 3, 4, 5}, func(i int)bool {return i < 7}) 56 | // true 57 | 58 | All([]int{1, 2, 3, 4, 5}, func(i int)bool {return i % 2 == 0}) 59 | // false 60 | 61 | ``` 62 | 63 | ### Any 64 | - Returns true if at least one element returns true for given predicate 65 | ```go 66 | Any([]int{1, 2, 3}, func(i int)bool {return i%2==0}) 67 | // true 68 | 69 | Any([]int{1, 2, 3}, func(i int)bool {return i > 7}) 70 | // false 71 | ``` 72 | 73 | ### AppendToGroup 74 | - Adds the key, value to the given map where each key maps to a slice of values 75 | ```go 76 | group := make(map[string][]int) 77 | 78 | AppendToGroup(grp, "a", 1) 79 | AppendToGroup(grp, "b", 2) 80 | AppendToGroup(grp, "a", 10) 81 | AppendToGroup(grp, "b", 20) 82 | AppendToGroup(grp, "a", 100) 83 | AppendToGroup(grp, "b", 200) 84 | 85 | // {"a":[1, 10, 100], "b":[2, 20, 200]} 86 | ``` 87 | 88 | ### Associate 89 | - Returns a map containing key-value pairs returned by the given function applied to the elements of the given slice 90 | ```go 91 | Associate([]int{1, 2, 3, 4}, func(i int) (string, int) { 92 | return fmt.Sprintf("M%d", i), i * 10 93 | }) 94 | // {"M1": 10, "M2": 20, "M3": 30, "M4": 40} 95 | ``` 96 | 97 | ### Chunked 98 | - Splits the slice into a slice of slices, each not exceeding given chunk size 99 | - The last slice might have fewer elements than the given chunk size 100 | ```go 101 | Chunked([]int{1, 2, 3, 4, 5, 6, 7, 8, 9}, 2) 102 | // [[1, 2], [3, 4], [5, 6], [7, 8], [9]] 103 | ``` 104 | 105 | ### ChunkedBy 106 | - Splits the slice into a slice of slices, starting a new sub slice whenever the callback function returns false. 107 | - The callback function is passed the previous and current element. 108 | ```go 109 | input := []int{10, 20, 30, 40, 31, 31, 33, 34, 21, 22, 23, 24, 11, 12, 13, 14} 110 | ChunkedBy(input, func(prev, next int) bool { return prev < next}) 111 | // [[10, 20, 30, 40], [31], [31, 33, 34], [21, 22, 23, 24], [11, 12, 13, 14]] 112 | ``` 113 | 114 | ### Distinct 115 | - Returns a slice containing only distinct elements from the given slice 116 | ```go 117 | Distinct([]int{1, 1, 2, 3, 3, 4, 4, 4, 4, 5, 5, 5}) 118 | // [1, 2, 3, 4, 5] 119 | ``` 120 | 121 | ### DistinctBy 122 | - Returns a slice containing only distinct elements from the given slice as distinguished by the given selector function 123 | ```go 124 | DistinctBy([]string{"a", "A", "b", "B", "c", "C"},func(s string) string { 125 | return strings.ToLower(s) 126 | }) 127 | // ["a", "b", "c"] 128 | ``` 129 | 130 | ### Drop 131 | - Returns a slice containing all elements except the first n. 132 | ```go 133 | // letters = ['a'..'z'] 134 | Drop(letters, 23) 135 | // ['x', 'y', 'z'] 136 | ``` 137 | 138 | ### DropLast 139 | - Returns a slice containing all elements except the last n. 140 | ```go 141 | // letters = ['a'..'z'] 142 | DropLast(letters, 23) 143 | // ['a', 'b', 'c'] 144 | ``` 145 | 146 | ### DropWhile 147 | - Returns a slice containing all elements except the first elements that satisfy the given predicate. 148 | ```go 149 | // letters = ['a'..'z'] 150 | DropWhile(letters, func(r rune) bool { return r < 'x' }) 151 | // ['x', 'y', 'z'] 152 | ``` 153 | 154 | ### DropLastWhile 155 | - Returns a slice containing all elements except the last elements that satisfy the given predicate. 156 | ```go 157 | // letters = ['a'..'z'] 158 | DropLastWhile(letters, func(r rune) bool { return r > 'c' }) 159 | // ['a', 'b', 'c'] 160 | ``` 161 | 162 | ### Filter 163 | - Returns the slice obtained after retaining only those elements in the given slice for which the given function returns true 164 | ```go 165 | Filter([]int{1, 2, 3, 4, 5, 6, 7, 8}, func(i int)bool {return i%2==0}) 166 | // [2, 4, 6, 8] 167 | ``` 168 | 169 | ### FilterIndexed 170 | - Returns the slice obtained after retaining only those elements in the given slice for which the given function returns true 171 | - Predicate function receives the value as well as its index in the slice. 172 | ```go 173 | FilterIndexed([]int{0, 1, 2, 3, 4, 8, 6}, func(index int, v int) bool { 174 | return index == v 175 | }) 176 | // [0, 1, 2, 3, 4, 6] 177 | ``` 178 | 179 | ### FilterMap 180 | - FilterMap returns the slice obtained after both filtering and mapping using the given function. 181 | - The function should return two values - the result of the mapping operation and whether the element should be included or dropped. 182 | - This is faster than doing separate filter and map operations, since it avoids extra allocations and slice traversals. 183 | - Inspired by std::iter::filter_map in Rust 184 | ```go 185 | FilterMap([]int{1, 2, 3, 4, 5}, 186 | func(i int) (int, bool) { 187 | if i%2 != 0 { 188 | return i, false // drop odd numbers 189 | } 190 | return i * i, true // square even numbers 191 | }) 192 | // [4, 16] 193 | ``` 194 | 195 | ### FlatMap 196 | - Applies the given function to each element in the input slice and combines all resulting slices into one. 197 | ```go 198 | FlatMap([]int{1, 2, 3, 4, 5}, func(i int) []int { return []int{i, i * i} }) 199 | // [1, 1, 2, 4, 3, 9, 4, 16, 5, 25] 200 | ``` 201 | 202 | ### FlatMapIndexed 203 | - Applies the given function to each element in the input slice, which also receives the element's index, and combines all resulting slices into one. 204 | ```go 205 | FlatMap([]int{1, 2, 3, 4, 5}, func(idx, val int) []int { return []int{idx, val * val} }) 206 | // 0, 1, 1, 4, 2, 9, 3, 16, 4, 25 207 | ``` 208 | 209 | 210 | 211 | ### Fold 212 | - Accumulates values starting with given initial value and applying given function to current accumulator and each element of the given slice. 213 | ```go 214 | Fold([]int{1, 2, 3, 4, 5}, func(acc, v int) int { return acc + v }) 215 | // 15 216 | ``` 217 | 218 | ### FoldIndexed 219 | - Accumulates values starting with given initial value and applying given function to current accumulator and each element of the given slice. 220 | - Function also receives index of current element. 221 | ```go 222 | FoldIndexed([]int{1, 2, 3, 4, 5}, func(index, acc, v int) int { 223 | return acc + index*v 224 | }) 225 | // 40 226 | ``` 227 | 228 | ### FoldItems 229 | - Accumulates values starting with given intial value and applying given function to current accumulator and each key, value of the given map. 230 | - Accumulator can be of any reference type. 231 | ```go 232 | m := map[int]int{1: 10, 2: 20, 3: 30} 233 | FoldItems(m, func(acc map[string]string, k, v int) map[string]string { 234 | acc[fmt.Sprintf("entry_%d", k)] = fmt.Sprintf("%d->%d", k, v) 235 | return acc 236 | }) 237 | // {"entry_1": "1->10", "entry_2": "2->20", "entry_3": "3->30"} 238 | ``` 239 | 240 | ### GetOrInsert 241 | - checks if a value corresponding to the given key is present in the map. 242 | - If present it returns the existing value. 243 | - If not present, it invokes the given callback function to get a new value for the given key, inserts it in the map and returns the new value 244 | ```go 245 | m := map[int]int{1:10, 2:20} 246 | GetOrInsert(m, 3, func(i int) int {return i * 10}) 247 | // returns 30; m is updated to {1:10, 2:20, 3:30}, 248 | ``` 249 | 250 | ### GroupBy 251 | - Returns a map where each key maps to slices of elements all having the same key as returned by the given function 252 | ```go 253 | GroupBy([]string{"a", "abc", "ab", "def", "abcd"}, func(s string) (int,string) { 254 | return len(s), s 255 | }) 256 | // {1: ["a"], 2: ["ab"], 3: ["abc", "def"], 4: ["abcd"]}, 257 | ``` 258 | 259 | ### Items 260 | - Returns the (key, value) pairs of the given map as a slice 261 | ```go 262 | // m := map[string][]int{"a": {1, 2, 3, 4}, "b": {1, 2}, "c": {1, 2, 3}} 263 | Items(m) 264 | // []*Pair[string, []int]{ 265 | // {"a", []int{1, 2, 3, 4}}, 266 | // {"b", []int{1, 2}}, 267 | // {"c", []int{1, 2, 3}}, 268 | // } 269 | ``` 270 | 271 | ### Map 272 | - Returns the slice obtained after applying the given function over every element in the given slice 273 | ```go 274 | Map([]int{1, 2, 3, 4, 5}, func(i int) int { return i * i }) 275 | // [1, 4, 9, 16, 25] 276 | ``` 277 | 278 | ### MapIndexed 279 | - Returns the slice obtained after applying the given function over every element in the given slice 280 | - The function also receives the index of each element in the slice. 281 | ```go 282 | MapIndexed([]int{1, 2, 3, 4, 5}, func(index, i int) int { return index * i }) 283 | // [0, 2, 6, 12, 20] 284 | ``` 285 | 286 | ### Partition 287 | - Returns two slices where the first slice contains elements for which the predicate returned true and the second slice contains elements for which it returned false. 288 | ```go 289 | type person struct { 290 | name string 291 | age int 292 | } 293 | 294 | tom := &person{"Tom", 18} 295 | andy := &person{"Andy", 32} 296 | sarah := &person{"Sarah", 22} 297 | 298 | Partition([]*person{tom, andy, sarah}, func(p *person) bool { return p.age < 30 }) 299 | // [tom, sarah], [andy] 300 | ``` 301 | 302 | ### Reduce 303 | - Accumulates the values starting with the first element and applying the operation from left to right to the current accumulator value and each element. 304 | - The input slice must have at least one element. 305 | ```go 306 | Reduce([]int{1, 2, 3, 4, 5}, func(acc, v int) int { return acc + v }) 307 | // 15 308 | ``` 309 | 310 | ### ReduceIndexed 311 | - Accumulates the values starting with the first element and applying the operation from left to right to the current accumulator value and each element. 312 | - The input slice must have at least one element. 313 | - The function also receives the index of each element. 314 | ```go 315 | ReduceIndexed([]string{"a", "b", "c", "d"}, func(index int, acc, v string) string { 316 | return fmt.Sprintf("%s%s%d", acc, v, index) 317 | }) 318 | // "ab1c2d3" 319 | ``` 320 | 321 | ### Reverse 322 | - Reverses the elements of the list in place. 323 | ```go 324 | // s = [1, 2, 3, 4, 5, 6, 7] 325 | Reverse(s) 326 | // s = [7, 6, 5, 4, 3, 2, 1] 327 | ``` 328 | 329 | ### Reversed 330 | - Returns a new list with the elements in reverse order. 331 | ```go 332 | // s = [1, 2, 3, 4, 5, 6, 7] 333 | r := Reversed(s) 334 | // r = [7, 6, 5, 4, 3, 2, 1] 335 | // s = [1, 2, 3, 4, 5, 6, 7] 336 | ``` 337 | 338 | ### Take 339 | - Returns the slice obtained after taking the first n elements from the given slice. 340 | ```go 341 | // letters = ['a'..'z'] 342 | Take(letters, 2) 343 | // ['a', 'b'] 344 | ``` 345 | 346 | ### TakeLast 347 | - Returns the slice obtained after taking the last n elements from the given slice. 348 | ```go 349 | // letters = ['a'..'z'] 350 | TakeLast(letters, 2) 351 | // ['y', 'z'] 352 | ``` 353 | 354 | ### TakeWhile 355 | - Returns a slice containing the first elements satisfying the given predicate 356 | ```go 357 | // letters = ['a'..'z'] 358 | TakeWhile(letters, func(s rune) bool { return s < 'f' }) 359 | // ['a', 'b', 'c', 'd', 'e'] 360 | ``` 361 | 362 | ### TakeLastWhile 363 | - Returns a slice containing the last elements satisfying the given predicate 364 | ```go 365 | // letters = ['a'..'z'] 366 | TakeLastWhile(letters, func(s rune) bool { return s > 'w' }) 367 | // ['x', 'y', 'z'] 368 | ``` 369 | 370 | ### TransformMap 371 | - Applies the given function to each key, value in the map, and returns a new map of the same type after transforming the keys and values depending on the callback functions return values. 372 | - If the last bool return value from the callback function is false, the entry is dropped 373 | ```go 374 | // filtering a map 375 | // m = {"a":[1, 2, 3, 4] "b":[1, 2] "c":[1, 2, 3]} 376 | TransformMap(m, 377 | func(k string, v []int) (string, []int, bool) { 378 | if len(v) < 3 { 379 | return k, v, false 380 | } 381 | return k, v, true 382 | }) 383 | // drops all values with length less than 3 384 | // {"a":[1, 2, 3, 4] "c":[1, 2, 3]} 385 | 386 | 387 | // transforming keys and values 388 | // m = {"a":[1, 2, 3, 4] "b":[5, 6]} 389 | TransformMap(m, 390 | func(k string, v []int) (string, []int, bool) { 391 | newK := strings.ToUpper(k) 392 | newV := Map(v, func(i int) int { return i * 10 }) 393 | return newK, newV, true 394 | }) 395 | // {"A":[10, 20, 30, 40] "B":[50, 60]} 396 | ``` 397 | 398 | 399 | ### Unzip 400 | - Returns two slices, where: 401 | - the first slice is built from the first values of each pair from the input slice 402 | - the second slice is built from the second values of each pair 403 | ```go 404 | Unzip([]*Pair[string, int]{{"a", 1}, {"b", 2}, {"c", 3}}) 405 | // ["a", "b", "c"], [1, 2, 3] 406 | ``` 407 | 408 | ### Windowed 409 | - Returns a slice of sliding windows, each of the given size, and with the given step 410 | - Several last slices may have fewer elements than the given size 411 | ```go 412 | Windowed([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, 5, 1) 413 | // [ 414 | // [1, 2, 3, 4, 5], 415 | // [2, 3, 4, 5, 6], 416 | // [3, 4, 5, 6, 7], 417 | // [4, 5, 6, 7, 8], 418 | // [5, 6, 7, 8, 9], 419 | // [6, 7, 8, 9, 10], 420 | // [7, 8, 9, 10], 421 | // [8, 9, 10], 422 | // [9, 10], 423 | // [10] 424 | // ] 425 | 426 | Windowed([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, 5, 3) 427 | // [ 428 | // [1, 2, 3, 4, 5], 429 | // [4, 5, 6, 7, 8], 430 | // [7, 8, 9, 10], 431 | // [10] 432 | // ] 433 | ``` 434 | 435 | ### Zip 436 | - Returns a slice of pairs from the elements of both slices with the same index 437 | - The returned slice has the length of the shortest input slice 438 | ```go 439 | Zip([]string{"a", "b", "c", "d"}, []int{1, 2, 3}) 440 | // []*Pair[string, int]{{"a", 1}, {"b", 2}, {"c", 3}} 441 | ``` 442 | -------------------------------------------------------------------------------- /slice.go: -------------------------------------------------------------------------------- 1 | package fun 2 | 3 | import "fmt" 4 | 5 | // All returns true if all elements return true for given predicate 6 | func All[T any](s []T, fn func(T) bool) bool { 7 | for _, e := range s { 8 | if !fn(e) { 9 | return false 10 | } 11 | } 12 | return true 13 | } 14 | 15 | // Any returns true if at least one element returns true for given predicate 16 | func Any[T any](s []T, fn func(T) bool) bool { 17 | for _, e := range s { 18 | if fn(e) { 19 | return true 20 | } 21 | } 22 | return false 23 | } 24 | 25 | // AppendToGroup adds the key, value to the given map where each key 26 | // points to a slice of values 27 | func AppendToGroup[M ~map[K][]V, K comparable, V any](m M, k K, v V) { 28 | lst, ok := m[k] 29 | if !ok { 30 | lst = make([]V, 0) 31 | } 32 | lst = append(lst, v) 33 | m[k] = lst 34 | } 35 | 36 | // Associate returns a map containing key-value pairs returned by the given 37 | // function applied to the elements of the given slice 38 | func Associate[T, V any, K comparable](s []T, fn func(T) (K, V)) map[K]V { 39 | ret := make(map[K]V) 40 | for _, e := range s { 41 | k, v := fn(e) 42 | ret[k] = v 43 | } 44 | return ret 45 | } 46 | 47 | // Chunked splits the slice into a slice of slices, each not exceeding given size 48 | // The last slice might have fewer elements than the given size 49 | func Chunked[T any](s []T, chunkSize int) [][]T { 50 | sz := len(s) 51 | ret := make([][]T, 0, sz/chunkSize+2) 52 | var sub []T 53 | for i := 0; i < sz; i++ { 54 | if i%chunkSize == 0 { 55 | if len(sub) > 0 { 56 | ret = append(ret, sub) 57 | } 58 | sub = make([]T, 0, chunkSize) 59 | } 60 | sub = append(sub, s[i]) 61 | } 62 | if len(sub) > 0 { 63 | ret = append(ret, sub) 64 | } 65 | return ret 66 | } 67 | 68 | func ChunkedBy[T any](s []T, fn func(T, T) bool) [][]T { 69 | ret := make([][]T, 0) 70 | switch len(s) { 71 | case 0: 72 | return ret 73 | case 1: 74 | ret = append(ret, []T{s[0]}) 75 | return ret 76 | } 77 | var currentSubList = []T{s[0]} 78 | for _, e := range s[1:] { 79 | if fn(currentSubList[len(currentSubList)-1], e) { 80 | currentSubList = append(currentSubList, e) 81 | } else { 82 | // save current sub list and start a new one 83 | ret = append(ret, currentSubList) 84 | currentSubList = []T{e} 85 | } 86 | } 87 | if len(currentSubList) > 0 { 88 | ret = append(ret, currentSubList) 89 | } 90 | return ret 91 | } 92 | 93 | // Distinct returns a slice containing only distinct elements from the given slice 94 | // Elements will retain their original order. 95 | func Distinct[T comparable](s []T) []T { 96 | m := make(map[T]bool) 97 | ret := make([]T, 0) 98 | for _, e := range s { 99 | _, ok := m[e] 100 | if ok { 101 | continue 102 | } 103 | m[e] = true 104 | ret = append(ret, e) 105 | } 106 | return ret 107 | } 108 | 109 | // DistinctBy returns a slice containing only distinct elements from the 110 | // given slice as distinguished by the given selector function 111 | // Elements will retain their original order. 112 | func DistinctBy[T any, K comparable](s []T, fn func(T) K) []T { 113 | m := make(map[K]bool) 114 | ret := make([]T, 0) 115 | for _, e := range s { 116 | k := fn(e) 117 | _, ok := m[k] 118 | if ok { 119 | continue 120 | } 121 | m[k] = true 122 | ret = append(ret, e) 123 | } 124 | return ret 125 | } 126 | 127 | // Drop returns a slice containing all elements except the first n 128 | func Drop[T any](s []T, n int) []T { 129 | if n >= len(s) { 130 | return make([]T, 0) 131 | } 132 | return s[n:] 133 | } 134 | 135 | // DropLast returns a slice containing all elements except the last n 136 | func DropLast[T any](s []T, n int) []T { 137 | if n >= len(s) { 138 | return make([]T, 0) 139 | } 140 | return s[:len(s)-n] 141 | } 142 | 143 | // DropLastWhile returns a slice containing all elements except the last elements 144 | // that satisfy the given predicate 145 | func DropLastWhile[T any](s []T, fn func(T) bool) []T { 146 | if len(s) == 0 { 147 | return s 148 | } 149 | i := len(s) - 1 150 | for ; i >= 0; i-- { 151 | if !fn(s[i]) { 152 | break 153 | } 154 | } 155 | return s[:i+1] 156 | } 157 | 158 | // DropWhile returns a slice containing all elements except the first elements 159 | // that satisfy the given predicate 160 | func DropWhile[T any](s []T, fn func(T) bool) []T { 161 | if len(s) == 0 { 162 | return s 163 | } 164 | i := 0 165 | for ; i < len(s); i++ { 166 | if !fn(s[i]) { 167 | break 168 | } 169 | } 170 | return s[i:] 171 | } 172 | 173 | // Filter returns the slice obtained after retaining only those elements 174 | // in the given slice for which the given function returns true 175 | func Filter[T any](s []T, fn func(T) bool) []T { 176 | ret := make([]T, 0) 177 | for _, e := range s { 178 | if fn(e) { 179 | ret = append(ret, e) 180 | } 181 | } 182 | return ret 183 | } 184 | 185 | // FilterIndexed returns the slice obtained after retaining only those elements 186 | // in the given slice for which the given function returns true. Predicate 187 | // receives the value as well as its index in the slice. 188 | func FilterIndexed[T any](s []T, fn func(int, T) bool) []T { 189 | ret := make([]T, 0) 190 | for i, e := range s { 191 | if fn(i, e) { 192 | ret = append(ret, e) 193 | } 194 | } 195 | return ret 196 | } 197 | 198 | // FilterMap returns the slice obtained after both filtering and mapping using 199 | // the given function. The function should return two values - 200 | // first, the result of the mapping operation and 201 | // second, whether the element should be included or not. 202 | // This is faster than doing a separate filter and map operations, 203 | // since it avoids extra allocations and slice traversals. 204 | func FilterMap[T1, T2 any]( 205 | s []T1, 206 | fn func(T1) (T2, bool), 207 | ) []T2 { 208 | 209 | ret := make([]T2, 0) 210 | for _, e := range s { 211 | m, ok := fn(e) 212 | if ok { 213 | ret = append(ret, m) 214 | } 215 | } 216 | return ret 217 | } 218 | 219 | // FlatMap transforms a slice of T1 elementss (s) into a slice of T2 elements. 220 | // The transformation is defined by the function fn, which takes a T1 element and returns a slice of T2 elements. 221 | // This function applies fn to every element in s, 222 | // and combines the results into a single, "flattened" slice of T2 elements. 223 | func FlatMap[T1, T2 any](s []T1, fn func(T1) []T2) []T2 { 224 | var ret []T2 225 | for _, e := range s { 226 | ret = append(ret, fn(e)...) 227 | } 228 | return ret 229 | } 230 | 231 | // FlatMapIndexed transforms a slice of T1 elements (s) into a slice of T2 elements. 232 | // The transformation is defined by the function fn, which takes a T1 element and the index to the element, and 233 | // returns a slice of T2 elements. 234 | // This function applies fn to every element in s, and combines the results into a single, "flattened" slice of T2 elements. 235 | func FlatMapIndexed[T1, T2 any](s []T1, fn func(int, T1) []T2) []T2 { 236 | var ret []T2 237 | for i, e := range s { 238 | ret = append(ret, fn(i, e)...) 239 | } 240 | return ret 241 | } 242 | 243 | // Fold accumulates values starting with given initial value and applying 244 | // given function to current accumulator and each element. 245 | func Fold[T, R any](s []T, initial R, fn func(R, T) R) R { 246 | acc := initial 247 | for _, e := range s { 248 | acc = fn(acc, e) 249 | } 250 | return acc 251 | } 252 | 253 | // FoldIndexed accumulates values starting with given initial value and applying 254 | // given function to current accumulator and each element. Function also 255 | // receives index of current element. 256 | func FoldIndexed[T, R any](s []T, initial R, fn func(int, R, T) R) R { 257 | acc := initial 258 | for i, e := range s { 259 | acc = fn(i, acc, e) 260 | } 261 | return acc 262 | } 263 | 264 | // FoldItems accumulates values starting with given intial value and applying 265 | // given function to current accumulator and each key, value. 266 | func FoldItems[M ~map[K]V, K comparable, V, R any]( 267 | m M, 268 | initial R, 269 | fn func(R, K, V) R, 270 | ) R { 271 | acc := initial 272 | for k, v := range m { 273 | acc = fn(acc, k, v) 274 | } 275 | return acc 276 | } 277 | 278 | // GetOrInsert checks if a value corresponding to the given key is present 279 | // in the map. If present it returns the existing value. If not, it invokes the 280 | // given callback function to get a new value for the given key, inserts it in 281 | // the map and returns the new value 282 | func GetOrInsert[M ~map[K]V, K comparable, V any](m M, k K, fn func(K) V) V { 283 | v, ok := m[k] 284 | if ok { 285 | // present, return existing value 286 | return v 287 | } 288 | // not present; get value, insert in map and return the new value 289 | v = fn(k) 290 | m[k] = v 291 | return v 292 | } 293 | 294 | // GroupBy returns a map containing key to list of values 295 | // returned by the given function applied to the elements of the given slice 296 | func GroupBy[T, V any, K comparable]( 297 | s []T, 298 | fn func(T) (K, V), 299 | ) map[K][]V { 300 | ret := make(map[K][]V) 301 | for _, e := range s { 302 | k, v := fn(e) 303 | lst, ok := ret[k] 304 | if !ok { 305 | lst = make([]V, 0) 306 | } 307 | lst = append(lst, v) 308 | ret[k] = lst 309 | } 310 | return ret 311 | } 312 | 313 | // Items returns the (key, value) pairs of the given map as a slice 314 | func Items[M ~map[K]V, K comparable, V any](m M) []*Pair[K, V] { 315 | ret := make([]*Pair[K, V], 0, len(m)) 316 | for k, v := range m { 317 | ret = append(ret, &Pair[K, V]{k, v}) 318 | } 319 | return ret 320 | } 321 | 322 | // Map returns the slice obtained after applying the given function over every 323 | // element in the given slice 324 | func Map[T1, T2 any](s []T1, fn func(T1) T2) []T2 { 325 | ret := make([]T2, 0, len(s)) 326 | for _, e := range s { 327 | ret = append(ret, fn(e)) 328 | } 329 | return ret 330 | } 331 | 332 | // MapIndexed returns the slice obtained after applying the given function over every 333 | // element in the given slice. The function also receives the index of each 334 | // element in the slice. 335 | func MapIndexed[T1, T2 any](s []T1, fn func(int, T1) T2) []T2 { 336 | ret := make([]T2, 0, len(s)) 337 | for i, e := range s { 338 | ret = append(ret, fn(i, e)) 339 | } 340 | return ret 341 | } 342 | 343 | // Partition returns two slices where the first slice contains elements for 344 | // which the predicate returned true and the second slice contains elements for 345 | // which it returned false. 346 | func Partition[T any](s []T, fn func(T) bool) ([]T, []T) { 347 | trueList := make([]T, 0) 348 | falseList := make([]T, 0) 349 | for _, e := range s { 350 | if fn(e) { 351 | trueList = append(trueList, e) 352 | } else { 353 | falseList = append(falseList, e) 354 | } 355 | } 356 | return trueList, falseList 357 | } 358 | 359 | // Reduce accumulates the values starting with the first element and applying the 360 | // operation from left to right to the current accumulator value and each element 361 | // The input slice must have at least one element. 362 | func Reduce[T any](s []T, fn func(T, T) T) T { 363 | if len(s) == 1 { 364 | return s[0] 365 | } 366 | return Fold(s[1:], s[0], fn) 367 | } 368 | 369 | // ReduceIndexed accumulates the values starting with the first element and applying the 370 | // operation from left to right to the current accumulator value and each element 371 | // The input slice must have at least one element. The function also receives 372 | // the index of the element. 373 | func ReduceIndexed[T any](s []T, fn func(int, T, T) T) T { 374 | if len(s) == 1 { 375 | return s[0] 376 | } 377 | acc := s[0] 378 | for i, e := range s[1:] { 379 | acc = fn(i+1, acc, e) 380 | } 381 | return acc 382 | } 383 | 384 | // Reverse reverses the elements of the list in place 385 | func Reverse[T any](s []T) { 386 | for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 { 387 | s[i], s[j] = s[j], s[i] 388 | } 389 | } 390 | 391 | // Reversed returns a new list with the elements in reverse order 392 | func Reversed[T any](s []T) []T { 393 | ret := make([]T, 0, len(s)) 394 | for i := len(s) - 1; i >= 0; i-- { 395 | ret = append(ret, s[i]) 396 | } 397 | return ret 398 | } 399 | 400 | // Take returns the slice obtained after taking the first n elements from the 401 | // given slice. 402 | // If n is greater than the length of the slice, returns the entire slice 403 | func Take[T any](s []T, n int) []T { 404 | if len(s) <= n { 405 | return s 406 | } 407 | return s[:n] 408 | } 409 | 410 | // TakeLast returns the slice obtained after taking the last n elements from the 411 | // given slice. 412 | func TakeLast[T any](s []T, n int) []T { 413 | if len(s) <= n { 414 | return s 415 | } 416 | return s[len(s)-n:] 417 | } 418 | 419 | // TakeLastWhile returns a slice containing the last elements satisfying the given 420 | // predicate 421 | func TakeLastWhile[T any](s []T, fn func(T) bool) []T { 422 | if len(s) == 0 { 423 | return s 424 | } 425 | i := len(s) - 1 426 | for ; i >= 0; i-- { 427 | if !fn(s[i]) { 428 | break 429 | } 430 | } 431 | return s[i+1:] 432 | } 433 | 434 | // TakeWhile returns a list containing the first elements satisfying the 435 | // given predicate 436 | func TakeWhile[T any](s []T, fn func(T) bool) []T { 437 | if len(s) == 0 { 438 | return s 439 | } 440 | i := 0 441 | for ; i < len(s); i++ { 442 | if !fn(s[i]) { 443 | break 444 | } 445 | } 446 | return s[:i] 447 | } 448 | 449 | // TransformMap applies the given function to each key, value in the map, 450 | // and returns a new map of the same type after transforming the keys 451 | // and values depending on the callback functions return values. If the last 452 | // bool return value from the callback function is false, the entry is dropped 453 | func TransformMap[M ~map[K]V, K comparable, V any]( 454 | m M, 455 | fn func(k K, v V) (K, V, bool), 456 | ) M { 457 | ret := make(map[K]V) 458 | for k, v := range m { 459 | newK, newV, include := fn(k, v) 460 | if include { 461 | ret[newK] = newV 462 | } 463 | } 464 | return ret 465 | } 466 | 467 | // Unzip returns two slices, where the first slice is built from the first 468 | // values of each pair from the input slice, and the second slice is built 469 | // from the second values of each pair 470 | func Unzip[T1 any, T2 any](ps []*Pair[T1, T2]) ([]T1, []T2) { 471 | l := len(ps) 472 | s1 := make([]T1, 0, l) 473 | s2 := make([]T2, 0, l) 474 | for _, p := range ps { 475 | s1 = append(s1, p.Fst) 476 | s2 = append(s2, p.Snd) 477 | } 478 | return s1, s2 479 | } 480 | 481 | // Windowed returns a slice of sliding windows into the given slice of the 482 | // given size, and with the given step 483 | func Windowed[T any](s []T, size, step int) [][]T { 484 | ret := make([][]T, 0) 485 | sz := len(s) 486 | if sz == 0 { 487 | return ret 488 | } 489 | start := 0 490 | end := 0 491 | updateEnd := func() { 492 | e := start + size 493 | if e >= sz { 494 | e = sz 495 | } 496 | end = e 497 | } 498 | updateStart := func() { 499 | s := start + step 500 | if s >= sz { 501 | s = sz 502 | } 503 | start = s 504 | } 505 | updateEnd() 506 | 507 | for { 508 | sub := make([]T, 0, end) 509 | for i := start; i < end; i++ { 510 | sub = append(sub, s[i]) 511 | } 512 | ret = append(ret, sub) 513 | updateStart() 514 | updateEnd() 515 | if start == end { 516 | break 517 | } 518 | } 519 | return ret 520 | } 521 | 522 | // Zip returns a slice of pairs from the elements of both slices with the same 523 | // index. The returned slice has the length of the shortest input slice 524 | func Zip[T1 any, T2 any](s1 []T1, s2 []T2) []*Pair[T1, T2] { 525 | minLen := len(s1) 526 | if minLen > len(s2) { 527 | minLen = len(s2) 528 | } 529 | 530 | // Allocate enough space to avoid copies and extra allocations 531 | ret := make([]*Pair[T1, T2], 0, minLen) 532 | 533 | for i := 0; i < minLen; i++ { 534 | ret = append(ret, &Pair[T1, T2]{ 535 | Fst: s1[i], 536 | Snd: s2[i], 537 | }) 538 | } 539 | return ret 540 | } 541 | 542 | // Pair represents a generic pair of two values 543 | type Pair[T1, T2 any] struct { 544 | Fst T1 545 | Snd T2 546 | } 547 | 548 | func (p Pair[T1, T2]) String() string { 549 | return fmt.Sprintf("(%v, %v)", p.Fst, p.Snd) 550 | } 551 | -------------------------------------------------------------------------------- /slice_test.go: -------------------------------------------------------------------------------- 1 | package fun 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "sort" 7 | "strings" 8 | "testing" 9 | ) 10 | 11 | func TestAll(t *testing.T) { 12 | type args struct { 13 | elems []int 14 | fn func(int) bool 15 | } 16 | tests := []struct { 17 | name string 18 | args args 19 | want bool 20 | }{ 21 | {"positive case", 22 | args{ 23 | []int{1, 2, 3, 4, 5}, 24 | func(i int) bool { return i < 10 }, 25 | }, 26 | true, 27 | }, 28 | {"negative case", 29 | args{ 30 | []int{1, 2, 3, 4, 5}, 31 | func(i int) bool { return i%2 == 0 }, 32 | }, 33 | false, 34 | }, 35 | } 36 | for _, tt := range tests { 37 | t.Run(tt.name, func(t *testing.T) { 38 | if got := All(tt.args.elems, tt.args.fn); got != tt.want { 39 | t.Errorf("All() = %v, want %v", got, tt.want) 40 | } 41 | }) 42 | } 43 | } 44 | 45 | func TestAppendToGroup(t *testing.T) { 46 | grp := make(map[string][]int) 47 | AppendToGroup(grp, "a", 1) 48 | AppendToGroup(grp, "b", 2) 49 | AppendToGroup(grp, "a", 10) 50 | AppendToGroup(grp, "b", 20) 51 | AppendToGroup(grp, "a", 100) 52 | AppendToGroup(grp, "b", 200) 53 | 54 | want := map[string][]int{ 55 | "a": {1, 10, 100}, 56 | "b": {2, 20, 200}, 57 | } 58 | if !reflect.DeepEqual(grp, want) { 59 | t.Errorf("AppendToGroup() = %v, want %v", grp, want) 60 | } 61 | } 62 | 63 | func TestAny(t *testing.T) { 64 | type args struct { 65 | elems []int 66 | fn func(int) bool 67 | } 68 | tests := []struct { 69 | name string 70 | args args 71 | want bool 72 | }{ 73 | {"positive case", 74 | args{ 75 | []int{1, 2, 3, 4, 5, 6}, 76 | func(i int) bool { return i%2 == 0 }, 77 | }, 78 | true, 79 | }, 80 | {"negative case", 81 | args{ 82 | []int{1, 2, 3, 4, 5, 6}, 83 | func(i int) bool { return i > 7 }, 84 | }, 85 | false, 86 | }, 87 | } 88 | for _, tt := range tests { 89 | t.Run(tt.name, func(t *testing.T) { 90 | if got := Any(tt.args.elems, tt.args.fn); got != tt.want { 91 | t.Errorf("Any() = %v, want %v", got, tt.want) 92 | } 93 | }) 94 | } 95 | } 96 | 97 | func TestFilter(t *testing.T) { 98 | type args struct { 99 | elems []int 100 | fn func(int) bool 101 | } 102 | tests := []struct { 103 | name string 104 | args args 105 | want []int 106 | }{ 107 | {"test filtering", 108 | args{ 109 | []int{1, 2, 3, 4, 5, 6, 7, 8}, 110 | func(i int) bool { return i%2 == 0 }, 111 | }, 112 | []int{2, 4, 6, 8}, 113 | }, 114 | } 115 | for _, tt := range tests { 116 | t.Run(tt.name, func(t *testing.T) { 117 | if got := Filter(tt.args.elems, tt.args.fn); !reflect.DeepEqual( 118 | got, 119 | tt.want, 120 | ) { 121 | t.Errorf("Filter() = %v, want %v", got, tt.want) 122 | } 123 | }) 124 | } 125 | } 126 | 127 | func TestAssociate(t *testing.T) { 128 | type args struct { 129 | elems []int 130 | fn func(int) (string, int) 131 | } 132 | tests := []struct { 133 | name string 134 | args args 135 | want map[string]int 136 | }{ 137 | {"associate strings with ints", 138 | args{ 139 | []int{1, 2, 3, 4}, 140 | func(i int) (string, int) { 141 | return fmt.Sprintf("M%d", i), i * 10 142 | }, 143 | }, 144 | map[string]int{"M1": 10, "M2": 20, "M3": 30, "M4": 40}, 145 | }, 146 | } 147 | for _, tt := range tests { 148 | t.Run(tt.name, func(t *testing.T) { 149 | if got := Associate(tt.args.elems, tt.args.fn); !reflect.DeepEqual( 150 | got, 151 | tt.want, 152 | ) { 153 | t.Errorf("Associate() = %v, want %v", got, tt.want) 154 | } 155 | }) 156 | } 157 | } 158 | 159 | func TestChunked(t *testing.T) { 160 | type args struct { 161 | s []int 162 | n int 163 | } 164 | tests := []struct { 165 | name string 166 | args args 167 | want [][]int 168 | }{ 169 | {"exact multiple", 170 | args{ 171 | []int{1, 2, 3, 4, 5, 6, 7, 8, 9}, 172 | 3, 173 | }, 174 | [][]int{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}, 175 | }, 176 | {"extra elements", 177 | args{ 178 | []int{1, 2, 3, 4, 5, 6, 7, 8, 9}, 179 | 2, 180 | }, 181 | [][]int{{1, 2}, {3, 4}, {5, 6}, {7, 8}, {9}}, 182 | }, 183 | {"not enough elements", 184 | args{ 185 | []int{1, 2, 3}, 186 | 5, 187 | }, 188 | [][]int{{1, 2, 3}}, 189 | }, 190 | } 191 | for _, tt := range tests { 192 | t.Run(tt.name, func(t *testing.T) { 193 | if got := Chunked(tt.args.s, tt.args.n); !reflect.DeepEqual( 194 | got, 195 | tt.want, 196 | ) { 197 | t.Errorf("Chunked() = %v, want %v", got, tt.want) 198 | } 199 | }) 200 | } 201 | } 202 | 203 | func TestChunkedBy(t *testing.T) { 204 | input := []int{ 205 | 10, 206 | 20, 207 | 30, 208 | 40, 209 | 31, 210 | 31, 211 | 33, 212 | 34, 213 | 21, 214 | 22, 215 | 23, 216 | 24, 217 | 11, 218 | 12, 219 | 13, 220 | 14, 221 | } 222 | output := ChunkedBy(input, func(prev, next int) bool { 223 | return prev < next 224 | }) 225 | expected := [][]int{ 226 | {10, 20, 30, 40}, 227 | {31}, 228 | {31, 33, 34}, 229 | {21, 22, 23, 24}, 230 | {11, 12, 13, 14}, 231 | } 232 | if !reflect.DeepEqual(output, expected) { 233 | t.Errorf("ChunkedBy() = %v, want %v", output, expected) 234 | } 235 | } 236 | 237 | func TestDistinct(t *testing.T) { 238 | type args struct { 239 | s []int 240 | } 241 | tests := []struct { 242 | name string 243 | args args 244 | want []int 245 | }{ 246 | {"test distinct", 247 | args{ 248 | []int{1, 1, 2, 3, 3, 4, 4, 4, 4, 5, 5, 5}, 249 | }, 250 | []int{1, 2, 3, 4, 5}, 251 | }, 252 | } 253 | for _, tt := range tests { 254 | t.Run(tt.name, func(t *testing.T) { 255 | if got := Distinct(tt.args.s); !reflect.DeepEqual(got, tt.want) { 256 | t.Errorf("Distinct() = %v, want %v", got, tt.want) 257 | } 258 | }) 259 | } 260 | } 261 | 262 | func TestDistinctBy(t *testing.T) { 263 | type args struct { 264 | s []string 265 | fn func(string) string 266 | } 267 | tests := []struct { 268 | name string 269 | args args 270 | want []string 271 | }{{"test distinctBy", 272 | args{[]string{"a", "A", "b", "B", "c", "C"}, 273 | func(s string) string { return strings.ToLower(s) }, 274 | }, 275 | []string{"a", "b", "c"}, 276 | }} 277 | for _, tt := range tests { 278 | t.Run(tt.name, func(t *testing.T) { 279 | if got := DistinctBy(tt.args.s, tt.args.fn); !reflect.DeepEqual( 280 | got, 281 | tt.want, 282 | ) { 283 | t.Errorf("DistinctBy() = %v, want %v", got, tt.want) 284 | } 285 | }) 286 | } 287 | } 288 | 289 | func TestDrop(t *testing.T) { 290 | type args struct { 291 | s []int 292 | n int 293 | } 294 | tests := []struct { 295 | name string 296 | args args 297 | want []int 298 | }{ 299 | {"drop less than slice length", 300 | args{ 301 | []int{1, 2, 3, 4, 5, 6, 7, 8, 9}, 302 | 4, 303 | }, 304 | []int{5, 6, 7, 8, 9}, 305 | }, 306 | {"drop more than slice length", 307 | args{ 308 | []int{1, 2, 3, 4, 5, 6, 7, 8, 9}, 309 | 12, 310 | }, 311 | []int{}, 312 | }, 313 | {"drop slice length", 314 | args{ 315 | []int{1, 2, 3, 4, 5, 6, 7, 8, 9}, 316 | 9, 317 | }, 318 | []int{}, 319 | }, 320 | {"drop all but last", 321 | args{ 322 | []int{1, 2, 3, 4, 5, 6, 7, 8, 9}, 323 | 8, 324 | }, 325 | []int{9}, 326 | }, 327 | } 328 | for _, tt := range tests { 329 | t.Run(tt.name, func(t *testing.T) { 330 | if got := Drop(tt.args.s, tt.args.n); !reflect.DeepEqual( 331 | got, 332 | tt.want, 333 | ) { 334 | t.Errorf("Drop() = %v, want %v", got, tt.want) 335 | } 336 | }) 337 | } 338 | } 339 | 340 | func TestDropLast(t *testing.T) { 341 | type args struct { 342 | s []int 343 | n int 344 | } 345 | tests := []struct { 346 | name string 347 | args args 348 | want []int 349 | }{ 350 | {"drop less than slice length", 351 | args{ 352 | []int{1, 2, 3, 4, 5, 6, 7, 8, 9}, 353 | 4, 354 | }, 355 | []int{1, 2, 3, 4, 5}, 356 | }, 357 | {"drop more than slice length", 358 | args{ 359 | []int{1, 2, 3, 4, 5, 6, 7, 8, 9}, 360 | 12, 361 | }, 362 | []int{}, 363 | }, 364 | {"drop slice length", 365 | args{ 366 | []int{1, 2, 3, 4, 5, 6, 7, 8, 9}, 367 | 9, 368 | }, 369 | []int{}, 370 | }, 371 | {"drop all but last", 372 | args{ 373 | []int{1, 2, 3, 4, 5, 6, 7, 8, 9}, 374 | 8, 375 | }, 376 | []int{1}, 377 | }, 378 | } 379 | for _, tt := range tests { 380 | t.Run(tt.name, func(t *testing.T) { 381 | if got := DropLast(tt.args.s, tt.args.n); !reflect.DeepEqual( 382 | got, 383 | tt.want, 384 | ) { 385 | t.Errorf("DropLast() = %v, want %v", got, tt.want) 386 | } 387 | }) 388 | } 389 | } 390 | 391 | func TestDropLastWhile(t *testing.T) { 392 | type args struct { 393 | s []int 394 | fn func(int) bool 395 | } 396 | tests := []struct { 397 | name string 398 | args args 399 | want []int 400 | }{ 401 | { 402 | "test dropLastWhile", 403 | args{ 404 | []int{1, 2, 3, 4, 5, 6}, 405 | func(i int) bool { return i > 3 }, 406 | }, 407 | []int{1, 2, 3}, 408 | }, 409 | { 410 | "emtpy list", 411 | args{ 412 | []int{}, 413 | func(i int) bool { return i > 3 }, 414 | }, 415 | []int{}, 416 | }, 417 | { 418 | "drop all", 419 | args{ 420 | []int{1, 2, 3, 4, 5, 6}, 421 | func(i int) bool { return i > 0 }, 422 | }, 423 | []int{}, 424 | }, 425 | { 426 | "drop all but one", 427 | args{ 428 | []int{1, 2, 3, 4, 5, 6}, 429 | func(i int) bool { return i > 1 }, 430 | }, 431 | []int{1}, 432 | }, 433 | } 434 | for _, tt := range tests { 435 | t.Run(tt.name, func(t *testing.T) { 436 | if got := DropLastWhile(tt.args.s, tt.args.fn); !reflect.DeepEqual( 437 | got, 438 | tt.want, 439 | ) { 440 | t.Errorf("DropLastWhile() = %v, want %v", got, tt.want) 441 | } 442 | }) 443 | } 444 | } 445 | 446 | func TestDropWhile(t *testing.T) { 447 | type args struct { 448 | s []int 449 | fn func(int) bool 450 | } 451 | tests := []struct { 452 | name string 453 | args args 454 | want []int 455 | }{ 456 | {"less than slice len", 457 | args{ 458 | []int{1, 2, 3, 4, 5, 6, 7, 8}, 459 | func(i int) bool { return i < 5 }, 460 | }, 461 | []int{5, 6, 7, 8}, 462 | }, 463 | {"empty slice", 464 | args{ 465 | []int{}, 466 | func(i int) bool { return i < 5 }, 467 | }, 468 | []int{}, 469 | }, 470 | 471 | {"drop all", 472 | args{ 473 | []int{1, 2, 3, 4, 5, 6, 7, 8}, 474 | func(i int) bool { return i < 9 }, 475 | }, 476 | []int{}, 477 | }, 478 | {"drop all but one", 479 | args{ 480 | []int{1, 2, 3, 4, 5, 6, 7, 8}, 481 | func(i int) bool { return i < 8 }, 482 | }, 483 | []int{8}, 484 | }, 485 | } 486 | for _, tt := range tests { 487 | t.Run(tt.name, func(t *testing.T) { 488 | if got := DropWhile(tt.args.s, tt.args.fn); !reflect.DeepEqual( 489 | got, 490 | tt.want, 491 | ) { 492 | t.Errorf("DropWhile() = %v, want %v", got, tt.want) 493 | } 494 | }) 495 | } 496 | } 497 | 498 | func TestDropVariants(t *testing.T) { 499 | letters := alphabet() 500 | 501 | got := Drop(letters, 23) 502 | expected := []rune{'x', 'y', 'z'} 503 | if !reflect.DeepEqual(got, expected) { 504 | t.Errorf("Drop() = %v want %v", got, expected) 505 | } 506 | 507 | got = DropLast(letters, 23) 508 | expected = []rune{'a', 'b', 'c'} 509 | if !reflect.DeepEqual(got, expected) { 510 | t.Errorf("DropLast() = %v want %v", got, expected) 511 | } 512 | 513 | got = DropWhile(letters, func(r rune) bool { return r < 'x' }) 514 | expected = []rune{'x', 'y', 'z'} 515 | if !reflect.DeepEqual(got, expected) { 516 | t.Errorf("DropWhile() = %v want %v", got, expected) 517 | } 518 | 519 | got = DropLastWhile(letters, func(r rune) bool { return r > 'c' }) 520 | expected = []rune{'a', 'b', 'c'} 521 | if !reflect.DeepEqual(got, expected) { 522 | t.Errorf("DropLastWhile() = %v want %v", got, expected) 523 | } 524 | } 525 | 526 | func TestFilterIndexed(t *testing.T) { 527 | type args struct { 528 | s []int 529 | fn func(int, int) bool 530 | } 531 | tests := []struct { 532 | name string 533 | args args 534 | want []int 535 | }{ 536 | {"filter indexed", 537 | args{ 538 | []int{0, 1, 2, 3, 4, 8, 6}, 539 | func(index int, v int) bool { return index == v }, 540 | }, 541 | []int{0, 1, 2, 3, 4, 6}, 542 | }, 543 | } 544 | for _, tt := range tests { 545 | t.Run(tt.name, func(t *testing.T) { 546 | if got := FilterIndexed(tt.args.s, tt.args.fn); !reflect.DeepEqual( 547 | got, 548 | tt.want, 549 | ) { 550 | t.Errorf("FilterIndexed() = %v, want %v", got, tt.want) 551 | } 552 | }) 553 | } 554 | } 555 | 556 | func TestFold(t *testing.T) { 557 | type args struct { 558 | s []int 559 | initial int 560 | fn func(int, int) int 561 | } 562 | tests := []struct { 563 | name string 564 | args args 565 | want int 566 | }{ 567 | {"summation by fold", 568 | args{ 569 | []int{1, 2, 3, 4, 5}, 570 | 0, 571 | func(acc, v int) int { return acc + v }, 572 | }, 573 | 15, 574 | }, 575 | } 576 | for _, tt := range tests { 577 | t.Run(tt.name, func(t *testing.T) { 578 | if got := Fold(tt.args.s, tt.args.initial, tt.args.fn); !reflect.DeepEqual( 579 | got, 580 | tt.want, 581 | ) { 582 | t.Errorf("Fold() = %v, want %v", got, tt.want) 583 | } 584 | }) 585 | } 586 | } 587 | 588 | func TestFoldIndexed(t *testing.T) { 589 | type args struct { 590 | s []int 591 | initial int 592 | fn func(int, int, int) int 593 | } 594 | tests := []struct { 595 | name string 596 | args args 597 | want int 598 | }{ 599 | {"fold indexed", 600 | args{ 601 | []int{1, 2, 3, 4, 5}, 602 | 0, 603 | func(index, acc, v int) int { return acc + index*v }, 604 | }, 605 | 40, 606 | }, 607 | } 608 | for _, tt := range tests { 609 | t.Run(tt.name, func(t *testing.T) { 610 | if got := FoldIndexed(tt.args.s, tt.args.initial, tt.args.fn); !reflect.DeepEqual( 611 | got, 612 | tt.want, 613 | ) { 614 | t.Errorf("FoldIndexed() = %v, want %v", got, tt.want) 615 | } 616 | }) 617 | } 618 | } 619 | 620 | func TestGroupByWithOriginalTypesForKeyAndValue(t *testing.T) { 621 | 622 | input := []string{"a", "abc", "ab", "def", "abcd"} 623 | want := map[int][]string{ 624 | 1: {"a"}, 625 | 2: {"ab"}, 626 | 3: {"abc", "def"}, 627 | 4: {"abcd"}, 628 | } 629 | got := GroupBy(input, func(str string) (int, string) { 630 | return len(str), str 631 | }) 632 | if !reflect.DeepEqual(got, want) { 633 | t.Errorf("GroupBy() = %v, want %v", got, want) 634 | } 635 | } 636 | 637 | type wrapped struct { 638 | value string 639 | } 640 | 641 | func (w wrapped) String() string { 642 | return fmt.Sprintf("Wrapped:'%s'", w.value) 643 | } 644 | 645 | func TestGroupByWithNewTypesForKeyAndValue(t *testing.T) { 646 | 647 | input := []string{"a", "abc", "ab", "def", "abcd"} 648 | want := map[float64][]*wrapped{ 649 | 10.0: {&wrapped{"a"}}, 650 | 20.0: {&wrapped{"ab"}}, 651 | 30.0: {&wrapped{"abc"}, &wrapped{"def"}}, 652 | 40.0: {&wrapped{"abcd"}}, 653 | } 654 | got := GroupBy(input, func(str string) (float64, *wrapped) { 655 | return float64(len(str)) * 10.0, &wrapped{str} 656 | }) 657 | for k, vs := range want { 658 | avs, ok := got[k] 659 | if !ok { 660 | t.Errorf("expected key '%v' not found", k) 661 | return 662 | } 663 | if len(vs) != len(avs) { 664 | t.Errorf("expected %d elements for key:'%v'. got %d", 665 | len(vs), k, len(avs)) 666 | return 667 | } 668 | for i := 0; i < len(vs); i++ { 669 | av := avs[i] 670 | v := vs[i] 671 | if av.value != v.value { 672 | t.Errorf("expected value: %s, got %s", v, av) 673 | } 674 | } 675 | 676 | } 677 | 678 | } 679 | 680 | func TestItems(t *testing.T) { 681 | m := map[string][]int{ 682 | "a": {1, 2, 3, 4}, 683 | "b": {1, 2}, 684 | "c": {1, 2, 3}, 685 | } 686 | want := []*Pair[string, []int]{ 687 | {"a", []int{1, 2, 3, 4}}, 688 | {"b", []int{1, 2}}, 689 | {"c", []int{1, 2, 3}}, 690 | } 691 | sort.Slice(want, func(i, j int) bool { 692 | return want[i].Fst < want[j].Fst 693 | }) 694 | 695 | got := Items(m) 696 | sort.Slice(got, func(i, j int) bool { 697 | return got[i].Fst < got[j].Fst 698 | }) 699 | 700 | if got := Items(m); !reflect.DeepEqual(got, want) { 701 | t.Errorf("Items() = %v, want %v", got, want) 702 | } 703 | } 704 | 705 | func TestMap(t *testing.T) { 706 | type args struct { 707 | elems []int 708 | fn func(int) int 709 | } 710 | tests := []struct { 711 | name string 712 | args args 713 | want []int 714 | }{ 715 | {"test mapping", 716 | args{ 717 | []int{1, 2, 3, 4, 5}, 718 | func(i int) int { return i * i }, 719 | }, 720 | []int{1, 4, 9, 16, 25}, 721 | }, 722 | } 723 | for _, tt := range tests { 724 | t.Run(tt.name, func(t *testing.T) { 725 | if got := Map(tt.args.elems, tt.args.fn); !reflect.DeepEqual( 726 | got, 727 | tt.want, 728 | ) { 729 | t.Errorf("Map() = %v, want %v", got, tt.want) 730 | } 731 | }) 732 | } 733 | } 734 | 735 | func TestFlatMap(t *testing.T) { 736 | type args struct { 737 | elems []int 738 | fn func(int) []int 739 | } 740 | tests := []struct { 741 | name string 742 | args args 743 | want []int 744 | }{ 745 | {"test mapping", 746 | args{ 747 | []int{1, 2, 3, 4, 5}, 748 | func(i int) []int { return []int{i, i * i} }, 749 | }, 750 | []int{1, 1, 2, 4, 3, 9, 4, 16, 5, 25}, 751 | }, 752 | } 753 | for _, tt := range tests { 754 | t.Run(tt.name, func(t *testing.T) { 755 | got := FlatMap(tt.args.elems, tt.args.fn) 756 | if !reflect.DeepEqual(got, tt.want) { 757 | t.Errorf("FlatMap() = %v, want %v", got, tt.want) 758 | } 759 | }) 760 | } 761 | } 762 | 763 | func TestFlatMapIndexed(t *testing.T) { 764 | type args struct { 765 | elems []int 766 | fn func(idx, val int) []int 767 | } 768 | tests := []struct { 769 | name string 770 | args args 771 | want []int 772 | }{ 773 | {"test mapping", 774 | args{ 775 | []int{1, 2, 3, 4, 5}, 776 | func(idx, val int) []int { return []int{idx, val * val} }, 777 | }, 778 | []int{0, 1, 1, 4, 2, 9, 3, 16, 4, 25}, 779 | }, 780 | } 781 | for _, tt := range tests { 782 | t.Run(tt.name, func(t *testing.T) { 783 | got := FlatMapIndexed(tt.args.elems, tt.args.fn) 784 | if !reflect.DeepEqual(got, tt.want) { 785 | t.Errorf("FlatMap() = %v, want %v", got, tt.want) 786 | } 787 | }) 788 | } 789 | } 790 | 791 | func TestMapIndexed(t *testing.T) { 792 | type args struct { 793 | s []int 794 | fn func(int, int) int 795 | } 796 | tests := []struct { 797 | name string 798 | args args 799 | want []int 800 | }{ 801 | {"map indexed", 802 | args{ 803 | []int{1, 2, 3, 4, 5}, 804 | func(index, i int) int { return index * i }, 805 | }, 806 | []int{0, 2, 6, 12, 20}, 807 | }, 808 | } 809 | for _, tt := range tests { 810 | t.Run(tt.name, func(t *testing.T) { 811 | if got := MapIndexed(tt.args.s, tt.args.fn); !reflect.DeepEqual( 812 | got, 813 | tt.want, 814 | ) { 815 | t.Errorf("MapIndexed() = %v, want %v", got, tt.want) 816 | } 817 | }) 818 | } 819 | } 820 | 821 | func TestPartition(t *testing.T) { 822 | type person struct { 823 | name string 824 | age int 825 | } 826 | type args struct { 827 | s []*person 828 | fn func(*person) bool 829 | } 830 | tom := &person{"Tom", 18} 831 | andy := &person{"Andy", 32} 832 | sarah := &person{"Sarah", 22} 833 | tests := []struct { 834 | name string 835 | args args 836 | want []*person 837 | want1 []*person 838 | }{ 839 | {"partition", 840 | args{ 841 | []*person{tom, andy, sarah}, 842 | func(p *person) bool { return p.age < 30 }, 843 | }, 844 | []*person{tom, sarah}, 845 | []*person{andy}, 846 | }, 847 | } 848 | for _, tt := range tests { 849 | t.Run(tt.name, func(t *testing.T) { 850 | got, got1 := Partition(tt.args.s, tt.args.fn) 851 | if !reflect.DeepEqual(got, tt.want) { 852 | t.Errorf("Partition() got = %v, want %v", got, tt.want) 853 | } 854 | if !reflect.DeepEqual(got1, tt.want1) { 855 | t.Errorf("Partition() got1 = %v, want %v", got1, tt.want1) 856 | } 857 | }) 858 | } 859 | } 860 | 861 | func TestReduce(t *testing.T) { 862 | type args struct { 863 | s []int 864 | fn func(int, int) int 865 | } 866 | tests := []struct { 867 | name string 868 | args args 869 | want int 870 | }{ 871 | {"reduce", 872 | args{ 873 | []int{1, 2, 3, 4, 5}, 874 | func(acc, v int) int { return acc + v }, 875 | }, 876 | 15, 877 | }, 878 | } 879 | for _, tt := range tests { 880 | t.Run(tt.name, func(t *testing.T) { 881 | if got := Reduce(tt.args.s, tt.args.fn); !reflect.DeepEqual( 882 | got, 883 | tt.want, 884 | ) { 885 | t.Errorf("Reduce() = %v, want %v", got, tt.want) 886 | } 887 | }) 888 | } 889 | } 890 | 891 | func TestReduceIndexed(t *testing.T) { 892 | type args struct { 893 | s []string 894 | fn func(int, string, string) string 895 | } 896 | tests := []struct { 897 | name string 898 | args args 899 | want string 900 | }{ 901 | {"reduce indexed", 902 | args{ 903 | []string{"a", "b", "c", "d"}, 904 | func(index int, acc, v string) string { 905 | return fmt.Sprintf("%s%s%d", acc, v, index) 906 | }, 907 | }, 908 | 909 | "ab1c2d3", 910 | }, 911 | } 912 | for _, tt := range tests { 913 | t.Run(tt.name, func(t *testing.T) { 914 | if got := ReduceIndexed(tt.args.s, tt.args.fn); !reflect.DeepEqual( 915 | got, 916 | tt.want, 917 | ) { 918 | t.Errorf("ReduceIndexed() = %v, want %v", got, tt.want) 919 | } 920 | }) 921 | } 922 | } 923 | 924 | func TestReverse(t *testing.T) { 925 | s := []int{1, 2, 3, 4, 5, 6, 7} 926 | Reverse(s) 927 | want := []int{7, 6, 5, 4, 3, 2, 1} 928 | if !reflect.DeepEqual(s, want) { 929 | t.Errorf("Reversed() = %v, want %v", s, want) 930 | } 931 | } 932 | 933 | func TestReversed(t *testing.T) { 934 | type args struct { 935 | s []int 936 | } 937 | tests := []struct { 938 | name string 939 | args args 940 | want []int 941 | }{ 942 | {"reversed", 943 | args{ 944 | []int{1, 2, 3, 4, 5}, 945 | }, 946 | []int{5, 4, 3, 2, 1}, 947 | }, 948 | } 949 | for _, tt := range tests { 950 | t.Run(tt.name, func(t *testing.T) { 951 | if got := Reversed(tt.args.s); !reflect.DeepEqual(got, tt.want) { 952 | t.Errorf("Reversed() = %v, want %v", got, tt.want) 953 | } 954 | }) 955 | } 956 | } 957 | 958 | func TestTake(t *testing.T) { 959 | type args struct { 960 | elems []int 961 | n int 962 | } 963 | tests := []struct { 964 | name string 965 | args args 966 | want []int 967 | }{ 968 | {"take less than slice length", 969 | args{ 970 | []int{1, 2, 3, 4, 5, 6, 7, 8}, 971 | 4, 972 | }, 973 | []int{1, 2, 3, 4}, 974 | }, 975 | {"take more than slice length", 976 | args{ 977 | []int{1, 2, 3, 4, 5, 6, 7, 8}, 978 | 10, 979 | }, 980 | []int{1, 2, 3, 4, 5, 6, 7, 8}, 981 | }, 982 | {"take slice length", 983 | args{ 984 | []int{1, 2, 3, 4, 5, 6, 7, 8}, 985 | 8, 986 | }, 987 | []int{1, 2, 3, 4, 5, 6, 7, 8}, 988 | }, 989 | {"empty list", 990 | args{ 991 | []int{}, 992 | 8, 993 | }, 994 | []int{}, 995 | }, 996 | {"take 0 elements", 997 | args{ 998 | []int{1, 2, 3, 4, 5, 6, 7, 8}, 999 | 0, 1000 | }, 1001 | []int{}, 1002 | }, 1003 | } 1004 | for _, tt := range tests { 1005 | t.Run(tt.name, func(t *testing.T) { 1006 | if got := Take(tt.args.elems, tt.args.n); !reflect.DeepEqual( 1007 | got, 1008 | tt.want, 1009 | ) { 1010 | t.Errorf("Take() = %v, want %v", got, tt.want) 1011 | } 1012 | }) 1013 | } 1014 | } 1015 | 1016 | func TestTakeLast(t *testing.T) { 1017 | type args struct { 1018 | s []int 1019 | n int 1020 | } 1021 | tests := []struct { 1022 | name string 1023 | args args 1024 | want []int 1025 | }{ 1026 | {"less than slice len", 1027 | args{ 1028 | []int{1, 2, 3, 4, 5, 6, 7, 8}, 1029 | 2, 1030 | }, 1031 | []int{7, 8}, 1032 | }, 1033 | {"more than slice len", 1034 | args{ 1035 | []int{1, 2, 3, 4, 5, 6, 7, 8}, 1036 | 9, 1037 | }, 1038 | []int{1, 2, 3, 4, 5, 6, 7, 8}, 1039 | }, 1040 | {"exactly slice len", 1041 | args{ 1042 | []int{1, 2, 3, 4, 5, 6, 7, 8}, 1043 | 8, 1044 | }, 1045 | []int{1, 2, 3, 4, 5, 6, 7, 8}, 1046 | }, 1047 | {"empty list", 1048 | args{ 1049 | []int{}, 1050 | 8, 1051 | }, 1052 | []int{}, 1053 | }, 1054 | {"take 0 elements", 1055 | args{ 1056 | []int{1, 2, 3, 4, 5, 6, 7, 8}, 1057 | 0, 1058 | }, 1059 | []int{}, 1060 | }, 1061 | } 1062 | for _, tt := range tests { 1063 | t.Run(tt.name, func(t *testing.T) { 1064 | if got := TakeLast(tt.args.s, tt.args.n); !reflect.DeepEqual( 1065 | got, 1066 | tt.want, 1067 | ) { 1068 | t.Errorf("TakeLast() = %v, want %v", got, tt.want) 1069 | } 1070 | }) 1071 | } 1072 | } 1073 | 1074 | func TestTake2(t *testing.T) { 1075 | got := Take(alphabet(), 2) 1076 | expected := []rune{'a', 'b'} 1077 | if !reflect.DeepEqual(got, expected) { 1078 | t.Errorf("TakeLast() = %v want %v", got, expected) 1079 | } 1080 | } 1081 | 1082 | func TestTakeLast2(t *testing.T) { 1083 | got := TakeLast(alphabet(), 2) 1084 | expected := []rune{'y', 'z'} 1085 | if !reflect.DeepEqual(got, expected) { 1086 | t.Errorf("TakeLast() = %v want %v", got, expected) 1087 | } 1088 | } 1089 | 1090 | func TestTakeLastWhile(t *testing.T) { 1091 | got := TakeLastWhile(alphabet(), func(s rune) bool { return s > 'w' }) 1092 | expected := []rune{'x', 'y', 'z'} 1093 | if !reflect.DeepEqual(got, expected) { 1094 | t.Errorf("TakeLastWhile() = %v want %v", got, expected) 1095 | } 1096 | } 1097 | 1098 | func TestTakeWhile(t *testing.T) { 1099 | got := TakeWhile(alphabet(), func(s rune) bool { return s < 'f' }) 1100 | expected := []rune{'a', 'b', 'c', 'd', 'e'} 1101 | if !reflect.DeepEqual(got, expected) { 1102 | t.Errorf("TakeWhile() = %v want %v", got, expected) 1103 | } 1104 | } 1105 | 1106 | func TestWindowed(t *testing.T) { 1107 | type args struct { 1108 | s []int 1109 | size int 1110 | step int 1111 | } 1112 | input := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} 1113 | tests := []struct { 1114 | name string 1115 | args args 1116 | want [][]int 1117 | }{ 1118 | {"size = 5, step = 1", 1119 | args{input, 5, 1}, 1120 | [][]int{ 1121 | {1, 2, 3, 4, 5}, 1122 | {2, 3, 4, 5, 6}, 1123 | {3, 4, 5, 6, 7}, 1124 | {4, 5, 6, 7, 8}, 1125 | {5, 6, 7, 8, 9}, 1126 | {6, 7, 8, 9, 10}, 1127 | {7, 8, 9, 10}, 1128 | {8, 9, 10}, 1129 | {9, 10}, 1130 | {10}, 1131 | }, 1132 | }, 1133 | {"size = 5, step = 3", 1134 | args{input, 5, 3}, 1135 | [][]int{ 1136 | {1, 2, 3, 4, 5}, 1137 | {4, 5, 6, 7, 8}, 1138 | {7, 8, 9, 10}, 1139 | {10}, 1140 | }, 1141 | }, 1142 | {"size = 3, step = 4", 1143 | args{input, 3, 4}, 1144 | [][]int{ 1145 | {1, 2, 3}, 1146 | {5, 6, 7}, 1147 | {9, 10}, 1148 | }, 1149 | }, 1150 | 1151 | {"slice smaller than size", 1152 | args{[]int{1, 2, 3}, 4, 1}, 1153 | [][]int{ 1154 | {1, 2, 3}, 1155 | {2, 3}, 1156 | {3}, 1157 | }, 1158 | }, 1159 | {"slice smaller than size and step", 1160 | args{[]int{1, 2, 3}, 4, 4}, 1161 | [][]int{ 1162 | {1, 2, 3}, 1163 | }, 1164 | }, 1165 | {"slice larger than size and smaller than step", 1166 | args{[]int{1, 2, 3}, 2, 4}, 1167 | [][]int{ 1168 | {1, 2}, 1169 | }, 1170 | }, 1171 | {"empty slice", 1172 | args{[]int{}, 4, 4}, 1173 | [][]int{}, 1174 | }, 1175 | } 1176 | for _, tt := range tests { 1177 | t.Run(tt.name, func(t *testing.T) { 1178 | if got := Windowed(tt.args.s, tt.args.size, tt.args.step); !reflect.DeepEqual( 1179 | got, 1180 | tt.want, 1181 | ) { 1182 | t.Errorf("Windowed() = %v, want %v", got, tt.want) 1183 | } 1184 | }) 1185 | } 1186 | } 1187 | 1188 | func TestZip(t *testing.T) { 1189 | s1 := []string{"a", "b", "c", "d"} 1190 | s2 := []int{1, 2, 3} 1191 | got := Zip(s1, s2) 1192 | want := []*Pair[string, int]{ 1193 | {"a", 1}, 1194 | {"b", 2}, 1195 | {"c", 3}, 1196 | } 1197 | if !reflect.DeepEqual(got, want) { 1198 | t.Errorf("Zip() = %v, want %v", got, want) 1199 | } 1200 | } 1201 | 1202 | func TestUnZip(t *testing.T) { 1203 | ps := []*Pair[string, int]{ 1204 | {"a", 1}, 1205 | {"b", 2}, 1206 | {"c", 3}, 1207 | } 1208 | want1 := []string{"a", "b", "c"} 1209 | want2 := []int{1, 2, 3} 1210 | got1, got2 := Unzip(ps) 1211 | if !reflect.DeepEqual(got1, want1) { 1212 | t.Errorf("Zip() first list = %v, want %v", got1, want1) 1213 | } 1214 | if !reflect.DeepEqual(got2, want2) { 1215 | t.Errorf("Zip() first list = %v, want %v", got2, want2) 1216 | } 1217 | } 1218 | 1219 | func TestFilterMap(t *testing.T) { 1220 | s := []int{1, 2, 3, 4, 5} 1221 | got := FilterMap(s, 1222 | func(i int) (int, bool) { 1223 | if i%2 != 0 { 1224 | return i, false // drop odd numbers 1225 | } 1226 | return i * i, true // square even numbers 1227 | }, 1228 | ) 1229 | want := []int{4, 16} 1230 | if !reflect.DeepEqual(got, want) { 1231 | t.Errorf("FilterMap() = %v, want %v", got, want) 1232 | } 1233 | } 1234 | 1235 | func alphabet() []rune { 1236 | ret := make([]rune, 0) 1237 | for r := 'a'; r <= 'z'; r++ { 1238 | ret = append(ret, r) 1239 | } 1240 | return ret 1241 | } 1242 | 1243 | func TestGetOrInsert(t *testing.T) { 1244 | 1245 | m := map[int]int{1: 10, 2: 20} 1246 | fn := func(k int) int { return k * 10 } 1247 | 1248 | // case where key is present 1249 | expected := 20 1250 | got := GetOrInsert(m, 2, fn) 1251 | if got != expected { 1252 | t.Errorf("GetOrInsert() = %d expected = %d", got, expected) 1253 | } 1254 | 1255 | // case where key is not present, but populated by invoking the function 1256 | expected = 30 1257 | got = GetOrInsert(m, 3, fn) 1258 | if got != expected { 1259 | t.Errorf("GetOrInsert() = %d expected = %d", got, expected) 1260 | } 1261 | 1262 | // check that the new value was stored in the map as well 1263 | expected = 30 1264 | got, ok := m[3] 1265 | if !ok { 1266 | t.Errorf("GetOrInsert did not insert new value in map!!") 1267 | } 1268 | if got != expected { 1269 | t.Errorf("value in map = %d expected = %d", got, expected) 1270 | } 1271 | 1272 | } 1273 | 1274 | func TestFoldItems(t *testing.T) { 1275 | type args struct { 1276 | m map[int]int 1277 | initial map[string]string 1278 | fn func(map[string]string, int, int) map[string]string 1279 | } 1280 | tests := []struct { 1281 | name string 1282 | args args 1283 | want map[string]string 1284 | }{ 1285 | { 1286 | "test fold over map items", 1287 | args{ 1288 | map[int]int{1: 10, 2: 20, 3: 30}, 1289 | make(map[string]string), 1290 | func(acc map[string]string, k, v int) map[string]string { 1291 | acc[fmt.Sprintf("entry_%d", k)] = fmt.Sprintf( 1292 | "%d->%d", 1293 | k, 1294 | v, 1295 | ) 1296 | return acc 1297 | }, 1298 | }, 1299 | map[string]string{ 1300 | "entry_1": "1->10", 1301 | "entry_2": "2->20", 1302 | "entry_3": "3->30", 1303 | }, 1304 | }, 1305 | } 1306 | for _, tt := range tests { 1307 | t.Run(tt.name, func(t *testing.T) { 1308 | if got := FoldItems(tt.args.m, tt.args.initial, tt.args.fn); !reflect.DeepEqual( 1309 | got, 1310 | tt.want, 1311 | ) { 1312 | t.Errorf("FoldItems() = %v, want %v", got, tt.want) 1313 | } 1314 | }) 1315 | } 1316 | } 1317 | 1318 | func TestTransformMap(t *testing.T) { 1319 | type args struct { 1320 | m map[string][]int 1321 | fn func(k string, v []int) (string, []int, bool) 1322 | } 1323 | tests := []struct { 1324 | name string 1325 | args args 1326 | want map[string][]int 1327 | }{ 1328 | { 1329 | "filter entries", 1330 | args{ 1331 | map[string][]int{ 1332 | "a": {1, 2, 3, 4}, 1333 | "b": {1, 2}, 1334 | "c": {1, 2, 3}, 1335 | }, 1336 | func(k string, v []int) (string, []int, bool) { 1337 | if len(v) < 3 { 1338 | return k, v, false 1339 | } 1340 | return k, v, true 1341 | }, 1342 | }, 1343 | map[string][]int{ 1344 | "a": {1, 2, 3, 4}, 1345 | "c": {1, 2, 3}, 1346 | }, 1347 | }, 1348 | { 1349 | "map entries", 1350 | args{ 1351 | map[string][]int{ 1352 | "a": {1, 2, 3, 4}, 1353 | "b": {5, 6}, 1354 | }, 1355 | func(k string, v []int) (string, []int, bool) { 1356 | newK := strings.ToUpper(k) 1357 | newV := Map(v, func(i int) int { return i * 10 }) 1358 | return newK, newV, true 1359 | }, 1360 | }, 1361 | map[string][]int{ 1362 | "A": {10, 20, 30, 40}, 1363 | "B": {50, 60}, 1364 | }, 1365 | }, 1366 | } 1367 | for _, tt := range tests { 1368 | t.Run(tt.name, func(t *testing.T) { 1369 | if got := TransformMap(tt.args.m, tt.args.fn); !reflect.DeepEqual( 1370 | got, 1371 | tt.want, 1372 | ) { 1373 | t.Errorf("TransformMap() = %v, want %v", got, tt.want) 1374 | } 1375 | }) 1376 | } 1377 | } 1378 | --------------------------------------------------------------------------------