├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ ├── codecov.yml │ └── go.yml ├── .gitignore ├── LICENSE ├── README.md ├── doc.go ├── example_test.go ├── go.mod ├── slices.go ├── slices_bench_test.go └── slices_test.go /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[BUG]" 5 | labels: bug 6 | assignees: srfrog 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior. 15 | 16 | **Expected behavior** 17 | A clear and concise description of what you expected to happen. 18 | 19 | **Screenshots** 20 | If applicable, add screenshots to help explain your problem. 21 | 22 | **Additional context** 23 | Add any other context about the problem here. 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "[FEATURE]" 5 | labels: enhancement 6 | assignees: srfrog 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/workflows/codecov.yml: -------------------------------------------------------------------------------- 1 | name: Codecov 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | 9 | jobs: 10 | cover: 11 | name: Build 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@master 15 | - uses: actions/setup-go@v2 16 | with: 17 | go-version: ^1.14 18 | - name: Get dependencies 19 | run: | 20 | go get -v -t -d ./... 21 | - name: Generate coverage report 22 | run: go test -cover -covermode=atomic -coverprofile=coverage.txt . 23 | - uses: codecov/codecov-action@v1 24 | with: 25 | token: ${{ secrets.CODECOV_TOKEN }} 26 | file: ./coverage.txt 27 | fail_ci_if_error: true 28 | -------------------------------------------------------------------------------- /.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 | 11 | build: 12 | name: Build 13 | runs-on: ubuntu-latest 14 | steps: 15 | 16 | - name: Set up Go 1.x 17 | uses: actions/setup-go@v2 18 | with: 19 | go-version: ^1.14 20 | 21 | - name: Check out code into the Go module directory 22 | uses: actions/checkout@v2 23 | 24 | - name: Get dependencies 25 | run: | 26 | go get -v -t -d ./... 27 | 28 | - name: Build 29 | run: go build -v . 30 | 31 | - name: Test 32 | run: go test -v . 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | ._* 3 | coverage.txt 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 srfrog - https://srfrog.me 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Slices 2 | [![PkgGoDev](https://pkg.go.dev/badge/github.com/srfrog/slices)](https://pkg.go.dev/github.com/srfrog/slices) 3 | [![Go Report Card](https://goreportcard.com/badge/github.com/srfrog/slices?svg=1)](https://goreportcard.com/report/github.com/srfrog/slices) 4 | [![codecov](https://codecov.io/gh/srfrog/slices/branch/master/graph/badge.svg?token=IDUWTIYYZQ)](https://codecov.io/gh/srfrog/slices) 5 | ![Build Status](https://github.com/srfrog/slices/workflows/Go/badge.svg) 6 | 7 | *Functions that operate on slices. Similar to functions from `package strings` or `package bytes` that have been adapted to work with slices.* 8 | 9 | ## Features 10 | 11 | - [x] Using a thin layer of idiomatic Go; correctness over performance. 12 | - [x] Provide most basic slice operations: index, trim, filter, map 13 | - [x] Some PHP favorites like: pop, push, shift, unshift, shuffle, etc... 14 | - [x] Non-destructive returns (won't alter original slice), except for explicit tasks. 15 | 16 | ## Quick Start 17 | 18 | Install using "go get": 19 | 20 | ```bash 21 | go get github.com/srfrog/slices 22 | ``` 23 | 24 | Then import from your source: 25 | 26 | ``` 27 | import "github.com/srfrog/slices" 28 | ``` 29 | 30 | View [example_test.go][1] for examples of basic usage and features. 31 | 32 | ## Documentation 33 | 34 | The full code documentation is located at GoDoc: 35 | 36 | [http://godoc.org/github.com/srfrog/slices](http://godoc.org/github.com/srfrog/slices) 37 | 38 | ## Usage 39 | 40 | This is a en example showing basic usage. 41 | 42 | ```go 43 | package main 44 | 45 | import( 46 | "fmt" 47 | 48 | "github.com/srfrog/slices" 49 | ) 50 | 51 | func main() { 52 | str := `Don't communicate by sharing memory - share memory by communicating` 53 | 54 | // Split string by spaces into a slice. 55 | slc := strings.Split(str, " ") 56 | 57 | // Count the number of "memory" strings in slc. 58 | memories := slices.Count(slc, "memory") 59 | fmt.Println("Memories:", memories) 60 | 61 | // Split slice into two parts. 62 | parts := slices.Split(slc, "-") 63 | fmt.Println("Split:", parts, len(parts)) 64 | 65 | // Compare second parts slice with original slc. 66 | diff := slices.Diff(slc, parts[1]) 67 | fmt.Println("Diff:", diff) 68 | 69 | // Chunk the slice 70 | chunks := slices.Chunk(parts[0], 1) 71 | fmt.Println("Chunk:", chunks) 72 | 73 | // Merge the parts 74 | merge := slices.Merge(chunks...) 75 | fmt.Println("Merge:", merge) 76 | } 77 | ``` 78 | 79 | [1]: https://github.com/srfrog/slices/blob/master/example_test.go 80 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 srfrog - https://srfrog.me 2 | // Use of this source code is governed by the license in the LICENSE file. 3 | 4 | // Package slices ... 5 | package slices 6 | 7 | // Version is the version of this package. 8 | const Version = "1.0.1" 9 | -------------------------------------------------------------------------------- /example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 srfrog - https://srfrog.me 2 | // Use of this source code is governed by the license in the LICENSE file. 3 | 4 | package slices_test 5 | 6 | import ( 7 | crand "crypto/rand" 8 | "fmt" 9 | "math/big" 10 | "strings" 11 | 12 | "github.com/srfrog/slices" 13 | ) 14 | 15 | // This example shows basic usage of various functions by manipulating 16 | // the slice 'slc'. 17 | func Example_basic() { 18 | str := `Don't communicate by sharing memory - share memory by communicating` 19 | 20 | // Split string by spaces into a slice. 21 | slc := strings.Split(str, " ") 22 | 23 | // Count the number of "memory" strings in slc. 24 | memories := slices.Count(slc, "memory") 25 | fmt.Println("Memories:", memories) 26 | 27 | // Split slice into two parts. 28 | parts := slices.Split(slc, "-") 29 | fmt.Println("Split:", parts, len(parts)) 30 | 31 | // Compare second parts slice with original slc. 32 | diff := slices.Diff(slc, parts[1]) 33 | fmt.Println("Diff:", diff) 34 | 35 | // Chunk the slice 36 | chunks := slices.Chunk(parts[0], 1) 37 | fmt.Println("Chunk:", chunks) 38 | 39 | // Merge the parts 40 | merge := slices.Merge(chunks...) 41 | fmt.Println("Merge:", merge) 42 | 43 | // OUTPUT: 44 | // Memories: 2 45 | // Split: [[Don't communicate by sharing memory] [share memory by communicating]] 2 46 | // Diff: [Don't communicate sharing -] 47 | // Chunk: [[Don't] [communicate] [by] [sharing] [memory]] 48 | // Merge: [Don't communicate by sharing memory] 49 | } 50 | 51 | func ExampleChunk() { 52 | // Top 3 programming languages 53 | data := `1,C,16%,2,Java,13%,3,Python,11%` 54 | slc := strings.Split(data, ",") 55 | 56 | // Split into chunks of 3. 57 | chunks := slices.Chunk(slc, 3) 58 | fmt.Println("Chunks:", chunks) 59 | 60 | // Replace C with Go once. 61 | chunks[0] = slices.Replace(chunks[0], "C", "Go", 1) 62 | fmt.Println("Replace:", chunks) 63 | 64 | // OUTPUT: 65 | // Chunks: [[1 C 16%] [2 Java 13%] [3 Python 11%]] 66 | // Replace: [[1 Go 16%] [2 Java 13%] [3 Python 11%]] 67 | } 68 | 69 | func ExampleShift() { 70 | slc := []string{"Go", "nuts", "for", "Go"} 71 | 72 | slices.Shift(&slc) // returns "Go" 73 | fmt.Println("Shift:", slc) 74 | 75 | slices.Unshift(&slc, "Really") // returns 4 76 | fmt.Println("Unshift:", slc) 77 | 78 | // OUTPUT: 79 | // Shift: [nuts for Go] 80 | // Unshift: [Really nuts for Go] 81 | } 82 | 83 | func ExampleRandFunc() { 84 | const ( 85 | chars = `abcdefghijklmnopqrstuvwxz` 86 | charsLen = len(chars) 87 | ) 88 | 89 | getIntn := func(max int) int { 90 | n, _ := crand.Int(crand.Reader, big.NewInt(int64(max))) 91 | return int(n.Int64()) 92 | } 93 | 94 | getRandStr := func() string { 95 | var sb strings.Builder 96 | 97 | m := getIntn(charsLen) 98 | for sb.Len() < m { 99 | sb.WriteByte(chars[getIntn(charsLen)]) 100 | } 101 | 102 | return sb.String() 103 | } 104 | 105 | // Generate a slice with 10 random strings. 106 | slc := slices.RepeatFunc(getRandStr, 10) 107 | fmt.Println("RepeatFunc len():", len(slc)) 108 | 109 | // Pick 3 random elements from slc. 110 | out := slices.RandFunc(slc, 3, getIntn) 111 | fmt.Println("RandFunc len():", len(out)) 112 | 113 | fmt.Println("ContainsAny:", slices.ContainsAny(slc, out)) 114 | 115 | // OUTPUT: 116 | // RepeatFunc len(): 10 117 | // RandFunc len(): 3 118 | // ContainsAny: true 119 | } 120 | 121 | func ExampleWalk() { 122 | var n int 123 | 124 | slc := []string{"Go", "go", "GO"} 125 | 126 | // Count the Go's. 127 | slices.Walk(slc, func(i int, v string) { 128 | if strings.EqualFold(v, "Go") { 129 | n++ 130 | } 131 | }) 132 | fmt.Println("Count:", n) 133 | 134 | // Convert a slice into a map. 135 | m := make(map[int]string) 136 | slices.Walk(slc, func(i int, v string) { 137 | m[i] = v 138 | }) 139 | fmt.Println("Mapize:", m) 140 | 141 | // OUTPUT: 142 | // Count: 3 143 | // Mapize: map[0:Go 1:go 2:GO] 144 | } 145 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/srfrog/slices 2 | 3 | go 1.15 4 | -------------------------------------------------------------------------------- /slices.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 srfrog - https://srfrog.me 2 | // Use of this source code is governed by the license in the LICENSE file. 3 | 4 | // Package slices is a collection of functions to operate with string slices. 5 | // Some functions were adapted from the strings package to work with slices, other 6 | // were ported from PHP 'array_*' function equivalents. 7 | package slices 8 | 9 | import ( 10 | "math/rand" 11 | "strings" 12 | ) 13 | 14 | // ValueFunc is a value comparison func, used to compare element values in a slice. 15 | type ValueFunc func(v string) bool 16 | 17 | // Compare returns an integer comparing two slices lexicographically. 18 | // The result will be 0 if a==b, or that a has all values of b. 19 | // The result will be -1 if a < b, or a is shorter than b. 20 | // The result will be +1 if a > b, or a is longer than b. 21 | // A nil argument is equivalent to an empty slice. 22 | func Compare(a, b []string) int { 23 | return CompareFunc(a, b, func(v1, v2 string) bool { return v1 == v2 }) 24 | } 25 | 26 | // CompareFunc returns an integer comparing two slices with func f. 27 | func CompareFunc(a, b []string, f func(string, string) bool) int { 28 | var i int 29 | 30 | m, n := len(a), len(b) 31 | switch { 32 | case m == 0: 33 | return -n 34 | case n == 0: 35 | return m 36 | case m > n: 37 | m = n 38 | } 39 | 40 | for i = 0; i < m; i++ { 41 | if !f(a[i], b[i]) { 42 | break 43 | } 44 | } 45 | 46 | return i - n 47 | } 48 | 49 | // Contains returns true if s is in a, false otherwise 50 | func Contains(a []string, s string) bool { 51 | return Index(a, s) != -1 52 | } 53 | 54 | // ContainsAny returns true if any value in b is in a, false otherwise 55 | func ContainsAny(a, b []string) bool { 56 | return IndexAny(a, b) != -1 57 | } 58 | 59 | // ContainsPrefix returns true if any element in a has prefix, false otherwise 60 | func ContainsPrefix(a []string, prefix string) bool { 61 | return IndexFunc(a, ValueHasPrefix(prefix)) != -1 62 | } 63 | 64 | // ContainsSuffix returns true if any element in a has suffix, false otherwise 65 | func ContainsSuffix(a []string, suffix string) bool { 66 | return IndexFunc(a, ValueHasSuffix(suffix)) != -1 67 | } 68 | 69 | // Count returns the number of occurrences of s in a. 70 | func Count(a []string, s string) int { 71 | if len(a) == 0 { 72 | return 0 73 | } 74 | 75 | var n int 76 | 77 | for i := range a { 78 | if a[i] == s { 79 | n++ 80 | } 81 | } 82 | 83 | return n 84 | } 85 | 86 | // Diff returns a slice with all the elements of b that are not found in a. 87 | func Diff(a, b []string) []string { 88 | return DiffFunc(a, b, func(ss []string, v string) bool { return !Contains(ss, v) }) 89 | } 90 | 91 | // DiffFunc compares the elements of b with those of b using f func. 92 | // It returns a slice of the elements in b that are not found in a where cmp() == true. 93 | func DiffFunc(a, b []string, f func([]string, string) bool) []string { 94 | var c []string 95 | 96 | for i := range a { 97 | if f(b, a[i]) { 98 | c = append(c, a[i]) 99 | } 100 | } 101 | 102 | return c 103 | } 104 | 105 | // Equal returns a boolean reporting whether a and b are the same length and contain the 106 | // same values, when compared lexicographically. 107 | func Equal(a, b []string) bool { 108 | return len(a) == len(b) && Compare(a, b) == 0 109 | } 110 | 111 | // EqualFold returns a boolean reporting whether a and b 112 | // are the same length and their values are equal under Unicode case-folding. 113 | func EqualFold(a, b []string) bool { 114 | return len(a) == len(b) && CompareFunc(a, b, strings.EqualFold) == 0 115 | } 116 | 117 | // Fill is an alias of Repeat. 118 | func Fill(n int, s string) []string { 119 | return Repeat(s, n) 120 | } 121 | 122 | // Filter returns a slice with all the elements of a that match string s. 123 | func Filter(a []string, s string) []string { 124 | return FilterFunc(a, ValueEquals(s)) 125 | } 126 | 127 | // FilterFunc returns a slice with all the elements of a that match string s that 128 | // satisfy f(s). If func f returns true, the value will be filtered from b. 129 | func FilterFunc(a []string, f ValueFunc) []string { 130 | if f == nil { 131 | return nil 132 | } 133 | 134 | var b []string 135 | 136 | for i := range a { 137 | if f(a[i]) { 138 | b = append(b, a[i]) 139 | } 140 | } 141 | 142 | return b 143 | } 144 | 145 | // FilterPrefix returns a slice with all the elements of a that have prefix. 146 | func FilterPrefix(a []string, prefix string) []string { 147 | return FilterFunc(a, ValueHasPrefix(prefix)) 148 | } 149 | 150 | // FilterSuffix returns a slice with all the elements of a that have suffix. 151 | func FilterSuffix(a []string, suffix string) []string { 152 | return FilterFunc(a, ValueHasSuffix(suffix)) 153 | } 154 | 155 | // Chunk will divide a slice into subslices with size elements into a new 2d slice. 156 | // The last chunk may contain less than size elements. If size less than 1, Chunk returns nil. 157 | func Chunk(a []string, size int) [][]string { 158 | if size < 1 { 159 | return nil 160 | } 161 | 162 | aa := make([][]string, 0, (len(a)+size-1)/size) 163 | for size <= len(a) { 164 | a, aa = a[size:], append(aa, a[0:size:size]) 165 | } 166 | 167 | if len(a) > 0 { 168 | aa = append(aa, a) 169 | } 170 | 171 | return aa 172 | } 173 | 174 | // Index returns the index of the first instance of s in a, or -1 if not found 175 | func Index(a []string, s string) int { 176 | return IndexFunc(a, ValueEquals(s)) 177 | } 178 | 179 | // IndexAny returns the index of the first instance of b in a, or -1 if not found 180 | func IndexAny(a, b []string) int { 181 | ret, m := -1, 0 182 | 183 | for idx := range genIndex(a, b, Index) { 184 | if idx == m { 185 | return idx 186 | } 187 | if ret == -1 || idx < ret { 188 | ret = idx 189 | } 190 | } 191 | 192 | return ret 193 | } 194 | 195 | // IndexFunc returns the index of the first element in a where f(s) == true, 196 | // or -1 if not found. 197 | func IndexFunc(a []string, f ValueFunc) int { 198 | for i := range a { 199 | if f(a[i]) { 200 | return i 201 | } 202 | } 203 | 204 | return -1 205 | } 206 | 207 | // Intersect returns a slice with all the elements of b that are found in b. 208 | func Intersect(a, b []string) []string { 209 | return DiffFunc(a, b, Contains) 210 | } 211 | 212 | // InsertAt inserts the values in slice a at index idx. 213 | // This func will append the values if idx doesn't fit in the slice or is negative. 214 | func InsertAt(a []string, idx int, values ...string) []string { 215 | m, n := len(a), len(values) 216 | if idx == -1 || idx > m { 217 | idx = m 218 | } 219 | 220 | if size := m + n; size <= cap(a) { 221 | b := a[:size] 222 | copy(b[idx+n:], a[idx:]) 223 | copy(b[idx:], values) 224 | 225 | return b 226 | } 227 | 228 | b := make([]string, m+n) 229 | copy(b, a[:idx]) 230 | copy(b[idx:], values) 231 | copy(b[idx+n:], a[idx:]) 232 | 233 | return b 234 | } 235 | 236 | // LastIndex returns the index of the last instance of s in a, or -1 if not found 237 | func LastIndex(a []string, s string) int { 238 | return LastIndexFunc(a, ValueEquals(s)) 239 | } 240 | 241 | // LastIndexAny returns the index of the last instance of b in a, or -1 if not found 242 | func LastIndexAny(a, b []string) int { 243 | ret, m := -1, len(a) 244 | 245 | for idx := range genIndex(a, b, LastIndex) { 246 | if idx == m { 247 | return idx 248 | } 249 | if idx != -1 && idx > ret { 250 | ret = idx 251 | } 252 | } 253 | 254 | return ret 255 | } 256 | 257 | // LastIndexFunc returns the index of the last element in a where f(s) == true, 258 | // or -1 if not found. 259 | func LastIndexFunc(a []string, f ValueFunc) int { 260 | for i := len(a) - 1; i >= 0; i-- { 261 | if f(a[i]) { 262 | return i 263 | } 264 | } 265 | 266 | return -1 267 | } 268 | 269 | // LastSearch returns the index of the last element containing substr in a, 270 | // or -1 if not found. An empty substr matches any. 271 | func LastSearch(a []string, substr string) int { 272 | return LastIndexFunc(a, ValueContains(substr)) 273 | } 274 | 275 | // Map returns a new slice with the function 'mapping' applied to each element of b 276 | func Map(mapping func(string) string, a []string) []string { 277 | if mapping == nil { 278 | return a 279 | } 280 | 281 | b := make([]string, len(a)) 282 | 283 | for i := range a { 284 | b[i] = mapping(a[i]) 285 | } 286 | 287 | return b 288 | } 289 | 290 | // Merge combines zero or many slices together, while preserving the order of elements. 291 | func Merge(aa ...[]string) []string { 292 | var a []string 293 | 294 | for i := range aa { 295 | a = append(a, aa[i]...) 296 | } 297 | 298 | return a 299 | } 300 | 301 | // Pop removes the last element in a and returns it, shortening the slice by one. 302 | // If a is empty returns empty string "". 303 | // Note that this function will change the slice pointed by a. 304 | func Pop(a *[]string) string { 305 | var s string 306 | 307 | if m := len(*a); m > 0 { 308 | s, *a = (*a)[m-1], (*a)[:m-1] 309 | } 310 | 311 | return s 312 | } 313 | 314 | // Push appends one or more values to a and returns the number of elements. 315 | // Note that this function will change the slice pointed by a. 316 | func Push(a *[]string, values ...string) int { 317 | if values != nil { 318 | *a = append(*a, values...) 319 | } 320 | 321 | return len(*a) 322 | } 323 | 324 | // Reduce applies the f func to each element in a and aggregates the result in acc 325 | // and returns the total of the iterations. If there is only one value in the slice, 326 | // it is returned. 327 | // This func panics if f func is nil, or if the slice is empty. 328 | func Reduce(a []string, f func(string, int, string) string) string { 329 | if f == nil { 330 | panic("slices: nil Reduce reducer func") 331 | } 332 | 333 | if len(a) == 0 { 334 | panic("slices: empty Reduce slice") 335 | } 336 | 337 | acc := a[0] 338 | 339 | Walk(a[1:], func(idx int, val string) { 340 | acc = f(acc, idx, val) 341 | }) 342 | 343 | return acc 344 | } 345 | 346 | // Repeat returns a slice consisting of count copies of s. 347 | func Repeat(s string, count int) []string { 348 | return RepeatFunc(func() string { return s }, count) 349 | } 350 | 351 | // RepeatFunc applies func f and returns a slice consisting of count values. 352 | func RepeatFunc(f func() string, count int) []string { 353 | if count < 0 { 354 | panic("slices: negative Repeat count") 355 | } 356 | 357 | a := make([]string, count) 358 | 359 | for i := range a { 360 | a[i] = f() 361 | } 362 | 363 | return a 364 | } 365 | 366 | // Replace returns a copy of the slice a with the first n instances of old replaced by new. 367 | // If n < 0, there is no limit on the number of replacements. 368 | func Replace(a []string, old, new string, n int) []string { 369 | m := len(a) 370 | if old == new || m == 0 || n == 0 { 371 | return a 372 | } 373 | 374 | if m := Count(a, old); m == 0 { 375 | return a 376 | } else if n < 0 || m < n { 377 | n = m 378 | } 379 | 380 | t := append(a[:0:0], a...) 381 | for i := 0; i < m; i++ { 382 | if n == 0 { 383 | break 384 | } 385 | if t[i] == old { 386 | t[i] = new 387 | n-- 388 | } 389 | } 390 | 391 | return t 392 | } 393 | 394 | // ReplaceAll returns a copy of the slice a with all instances of old replaced by new. 395 | func ReplaceAll(a []string, old, new string) []string { 396 | return Replace(a, old, new, -1) 397 | } 398 | 399 | // Rand returns a new slice with n number of random elements of a 400 | // using rand.Intn to select the elements. 401 | // Note: You may want initialize the rand seed once in your program. 402 | // 403 | // rand.Seed(time.Now().UnixNano()) 404 | // 405 | func Rand(a []string, n int) []string { 406 | return RandFunc(a, n, rand.Intn) 407 | } 408 | 409 | // RandFunc returns a new slice with n number of random elements of a 410 | // using func f to select the elements. 411 | func RandFunc(a []string, n int, f func(int) int) []string { 412 | b := make([]string, n) 413 | if m := len(a); m > 0 { 414 | for i := 0; i < n; i++ { 415 | b[i] = a[f(m)] 416 | } 417 | } 418 | 419 | return b 420 | } 421 | 422 | // Reverse returns a slice of the reverse index order elements of a. 423 | func Reverse(a []string) []string { 424 | for i, j := 0, len(a)-1; i < j; i, j = i+1, j-1 { 425 | a[i], a[j] = a[j], a[i] 426 | } 427 | 428 | return a 429 | } 430 | 431 | // Search returns the index of the first element containing substr in a, 432 | // or -1 if not found. An empty substr matches any. 433 | func Search(a []string, substr string) int { 434 | return IndexFunc(a, ValueContains(substr)) 435 | } 436 | 437 | // Shift shifts the first element of a and returns it, shortening the slice by one. 438 | // If a is empty returns empty string "". 439 | // Note that this function will change the slice pointed by a. 440 | func Shift(a *[]string) string { 441 | var s string 442 | 443 | if m := len(*a); m > 0 { 444 | s, *a = (*a)[0], (*a)[1:] 445 | } 446 | 447 | return s 448 | } 449 | 450 | // Shuffle returns a slice with randomized order of elements in a. 451 | // Note: You may want initialize the rand seed once in your program. 452 | // 453 | // rand.Seed(time.Now().UnixNano()) 454 | // 455 | func Shuffle(a []string) []string { 456 | if m := len(a); m > 1 { 457 | rand.Shuffle(m, func(i, j int) { 458 | a[i], a[j] = a[j], a[i] 459 | }) 460 | } 461 | 462 | return a 463 | } 464 | 465 | // Slice returns a subslice of the elements from the slice a as specified by the offset and length parameters. 466 | // 467 | // If offset > 0 the subslice will start at that offset in the slice. 468 | // If offset < 0 the subslice will start that far from the end of the slice. 469 | // 470 | // If length > 0 then the subslice will have up to that many elements in it. 471 | // If length == 0 then the subslice will begin from offset up to the end of the slice. 472 | // If length < 0 then the subslice will stop that many elements from the end of the slice. 473 | // If the slice a is shorter than the length, then only the available elements will be present. 474 | // 475 | // If the offset is larger than the size of the slice, an empty slice is returned. 476 | func Slice(a []string, offset, length int) []string { 477 | m := len(a) 478 | if length == 0 { 479 | length = m 480 | } 481 | 482 | switch { 483 | case offset > m: 484 | return nil 485 | case offset < 0 && (m+offset) < 0: 486 | offset = 0 487 | case offset < 0: 488 | offset = m + offset 489 | } 490 | 491 | switch { 492 | case length < 0: 493 | length = m - offset + length 494 | case offset+length > m: 495 | length = m - offset 496 | } 497 | 498 | if length <= 0 { 499 | return nil 500 | } 501 | 502 | return a[offset : offset+length] 503 | } 504 | 505 | // Splice removes a portion of the slice a and replace it with the elements of another. 506 | // 507 | // If offset > 0 then the start of the removed portion is at that offset from the beginning of the slice. 508 | // If offset < 0 then the start of the removed portion is at that offset from the end of the slice. 509 | // 510 | // If length > 0 then that many elements will be removed. 511 | // If length == 0 no elements will be removed. 512 | // If length == size removes everything from offset to the end of slice. 513 | // If length < 0 then the end of the removed portion will be that many elements from the end of the slice. 514 | // 515 | // If b == nil then length elements are removed from a at offset. 516 | // If b != nil then the elements are inserted at offset. 517 | // 518 | func Splice(a []string, offset, length int, b ...string) []string { 519 | m := len(a) 520 | switch { 521 | case offset > m: 522 | return a 523 | case offset < 0 && (m+offset) < 0: 524 | offset = 0 525 | case offset < 0: 526 | offset = m + offset 527 | } 528 | 529 | switch { 530 | case length < 0: 531 | length = m - offset + length 532 | case offset+length > m: 533 | length = m - offset 534 | } 535 | 536 | if length <= 0 { 537 | return a 538 | } 539 | 540 | return append(a[0:offset], append(b, a[offset+length:]...)...) 541 | } 542 | 543 | // split works almost like strings.genSplit() but for slices. 544 | func split(a []string, sep string, n int) [][]string { 545 | switch { 546 | case n == 0: 547 | return nil 548 | case sep == "": 549 | return Chunk(a, 1) 550 | case n < 0: 551 | n = Count(a, sep) + 1 552 | } 553 | 554 | aa, i := make([][]string, n+1), 0 555 | for i < n { 556 | m := Index(a, sep) 557 | if m < 0 { 558 | break 559 | } 560 | aa[i] = a[:m] 561 | a = a[m+1:] 562 | i++ 563 | } 564 | aa[i] = a 565 | 566 | return aa[:i+1] 567 | } 568 | 569 | // Split divides a slice a into subslices when any element matches the string sep. 570 | // 571 | // If a does not contain sep and sep is not empty, Split returns a 572 | // 2d slice of length 1 whose only element is a. 573 | // 574 | // If sep is empty, Split returns a 2d slice of all elements in a. 575 | // If a is nil and sep is empty, Split returns an empty slice. 576 | // 577 | // Split is akin to SplitN with a count of -1. 578 | func Split(a []string, sep string) [][]string { 579 | return split(a, sep, -1) 580 | } 581 | 582 | // Split divides a slice a into subslices when n elements match the string sep. 583 | // 584 | // The count determines the number of subslices to return: 585 | // n > 0: at most n subslices; the last element will be the unsplit remainder. 586 | // n == 0: the result is nil (zero subslices) 587 | // n < 0: all subslices 588 | // 589 | // For other cases, see the documentation for Split. 590 | func SplitN(a []string, sep string, n int) [][]string { 591 | return split(a, sep, n) 592 | } 593 | 594 | // Trim returns a slice with all the elements of a that don't match string s. 595 | func Trim(a []string, s string) []string { 596 | return TrimFunc(a, ValueEquals(s)) 597 | } 598 | 599 | // TrimFunc returns a slice with all the elements of a that don't match string s that 600 | // satisfy f(s). If func f returns true, the value will be trimmed from a. 601 | func TrimFunc(a []string, f ValueFunc) []string { 602 | if f == nil { 603 | return a 604 | } 605 | 606 | var b []string 607 | 608 | for i := range a { 609 | if !f(a[i]) { 610 | b = append(b, a[i]) 611 | } 612 | } 613 | 614 | return b 615 | } 616 | 617 | // TrimPrefix returns a slice with all the elements of a that don't have prefix. 618 | func TrimPrefix(a []string, prefix string) []string { 619 | return TrimFunc(a, ValueHasPrefix(prefix)) 620 | } 621 | 622 | // TrimSuffix returns a slice with all the elements of a that don't have suffix. 623 | func TrimSuffix(a []string, suffix string) []string { 624 | return TrimFunc(a, ValueHasSuffix(suffix)) 625 | } 626 | 627 | // Unique returns a slice with duplicate values removed. 628 | func Unique(a []string) []string { 629 | seen := make(map[string]struct{}) 630 | 631 | b := a[:0] 632 | for _, v := range a { 633 | if _, ok := seen[v]; !ok { 634 | seen[v] = struct{}{} 635 | b = append(b, v) 636 | } 637 | } 638 | 639 | return b 640 | } 641 | 642 | // Unshift prepends one or more elements to *a and returns the number of elements. 643 | // Note that this function will change the slice pointed by a 644 | func Unshift(a *[]string, s ...string) int { 645 | if s != nil { 646 | *a = append(s, *a...) 647 | } 648 | 649 | return len(*a) 650 | } 651 | 652 | // ValueContains returns true if element value v contains substr. 653 | func ValueContains(substr string) ValueFunc { 654 | return func(v string) bool { 655 | return strings.Contains(v, substr) 656 | } 657 | } 658 | 659 | // ValueEquals returns true if element value v equals s. 660 | func ValueEquals(s string) ValueFunc { 661 | return func(v string) bool { 662 | return v == s 663 | } 664 | } 665 | 666 | // ValueHasPrefix returns true if element value begins with prefix. 667 | func ValueHasPrefix(prefix string) ValueFunc { 668 | return func(v string) bool { 669 | return strings.HasPrefix(v, prefix) 670 | } 671 | } 672 | 673 | // ValueHasPrefix returns true if element value ends with suffix. 674 | func ValueHasSuffix(suffix string) ValueFunc { 675 | return func(v string) bool { 676 | return strings.HasSuffix(v, suffix) 677 | } 678 | } 679 | 680 | // Walk applies the f func to each element in a. 681 | func Walk(a []string, f func(idx int, val string)) { 682 | for idx := range a { 683 | f(idx, a[idx]) 684 | } 685 | } 686 | 687 | func genIndex(a, b []string, f func([]string, string) int) <-chan int { 688 | l := len(b) 689 | 690 | rc := make(chan int, l) 691 | go func() { 692 | defer close(rc) 693 | 694 | if l == 0 { 695 | rc <- -1 696 | return 697 | } 698 | 699 | for i := 0; i < l; i++ { 700 | rc <- f(a, b[i]) 701 | } 702 | }() 703 | 704 | return rc 705 | } 706 | -------------------------------------------------------------------------------- /slices_bench_test.go: -------------------------------------------------------------------------------- 1 | package slices 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | var ( 9 | resultSlice []string 10 | resultInt int 11 | 12 | s51 = "yyy" 13 | a100 = (func() []string { 14 | a := make([]string, 100) 15 | for i := 0; i < 100; i++ { 16 | if i == 50 { 17 | a[i] = s51 18 | continue 19 | } 20 | a[i] = "x" + fmt.Sprint(i) 21 | } 22 | return a 23 | })() 24 | aa100 = Split(a100, "") 25 | ) 26 | 27 | func benchmarkMerge(b *testing.B, f func(...[]string) []string) { 28 | var a []string 29 | for i := 0; i < b.N; i++ { 30 | a = f(aa100...) 31 | } 32 | resultSlice = a 33 | } 34 | 35 | func BenchmarkMerge(b *testing.B) { benchmarkMerge(b, Merge) } 36 | 37 | func benchmarkCount(b *testing.B, f func([]string, string) int) { 38 | var x int 39 | for i := 0; i < b.N; i++ { 40 | x = f(a100, s51) 41 | } 42 | resultInt = x 43 | } 44 | 45 | func BenchmarkCount(b *testing.B) { benchmarkCount(b, Count) } 46 | 47 | func BenchmarkCount2(b *testing.B) { 48 | benchmarkCount(b, 49 | func(a []string, s string) int { 50 | var n int 51 | for i, j := 0, len(a)-1; i <= j; i, j = i+1, j-1 { 52 | if a[i] == s { 53 | n++ 54 | } 55 | if i == j { 56 | break 57 | } 58 | if a[j] == s { 59 | n++ 60 | } 61 | } 62 | return n 63 | }) 64 | } 65 | 66 | func BenchmarkCount3(b *testing.B) { 67 | benchmarkCount(b, 68 | func(a []string, s string) int { 69 | n := 0 70 | for { 71 | i := Index(a, s) 72 | if i == -1 { 73 | return n 74 | } 75 | n++ 76 | a = a[i+1:] 77 | } 78 | }) 79 | } 80 | -------------------------------------------------------------------------------- /slices_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 srfrog - https://srfrog.me 2 | // Use of this source code is governed by the license in the LICENSE file. 3 | 4 | package slices 5 | 6 | import ( 7 | "reflect" 8 | "strings" 9 | "testing" 10 | ) 11 | 12 | var ( 13 | slc = []string{"Lorem", "ipsum", "dolor", "sit", "amet", "consectetur", "", "elit", "donec", "tempus", "Lorem"} 14 | ) 15 | 16 | func TestIndex(t *testing.T) { 17 | tests := []struct { 18 | in string 19 | out int 20 | }{ 21 | {in: "Lorem", out: 0}, 22 | {in: "dolor", out: 2}, 23 | {in: "horse", out: -1}, 24 | {in: "", out: 6}, 25 | } 26 | 27 | for _, tc := range tests { 28 | out := Index(slc, tc.in) 29 | if out != tc.out { 30 | t.Errorf("expecting %v got %v", tc.out, out) 31 | } 32 | } 33 | } 34 | 35 | func TestCount(t *testing.T) { 36 | tests := []struct { 37 | in []string 38 | s string 39 | out int 40 | }{ 41 | {in: slc, s: "Lorem", out: 2}, 42 | {in: slc, s: "srfrog", out: 0}, 43 | {in: slc, s: "", out: 1}, 44 | {in: nil, s: "Lorem", out: 0}, 45 | {in: []string{"", "Lorem", ""}, s: "Lorem", out: 1}, 46 | } 47 | 48 | for _, tc := range tests { 49 | out := Count(tc.in, tc.s) 50 | if out != tc.out { 51 | t.Errorf("expecting %v got %v", tc.out, out) 52 | } 53 | } 54 | } 55 | 56 | func TestMap(t *testing.T) { 57 | tests := []struct { 58 | in []string 59 | f func(string) string 60 | out []string 61 | }{ 62 | {in: slc[:5], f: func(s string) string { return s }, 63 | out: []string{"Lorem", "ipsum", "dolor", "sit", "amet"}}, 64 | {in: slc[:5], f: func(s string) string { return strings.Title(s) }, 65 | out: []string{"Lorem", "Ipsum", "Dolor", "Sit", "Amet"}}, 66 | {in: slc[:5], f: func(s string) string { return "XxX" + s }, 67 | out: []string{"XxXLorem", "XxXipsum", "XxXdolor", "XxXsit", "XxXamet"}}, 68 | {in: slc[:5], f: nil, 69 | out: []string{"Lorem", "ipsum", "dolor", "sit", "amet"}}, 70 | {in: nil, f: strings.Title, 71 | out: []string{}}, 72 | } 73 | 74 | for _, tc := range tests { 75 | out := Map(tc.f, tc.in) 76 | if !Equal(tc.out, out) { 77 | t.Errorf("expecting %v got %v", tc.out, out) 78 | } 79 | } 80 | } 81 | 82 | func TestTrimFunc(t *testing.T) { 83 | tests := []struct { 84 | in []string 85 | f func(string) bool 86 | out []string 87 | }{ 88 | {in: slc, f: func(s string) bool { return s == "Lorem" }, 89 | out: []string{"ipsum", "dolor", "sit", "amet", "consectetur", "", "elit", "donec", "tempus"}}, 90 | {in: slc, f: func(s string) bool { return s == "srfrog" }, 91 | out: slc}, 92 | {in: nil, f: func(s string) bool { return s == "srfrog" }, 93 | out: []string{}}, 94 | {in: slc, f: nil, out: slc}, 95 | } 96 | 97 | for _, tc := range tests { 98 | out := TrimFunc(tc.in, tc.f) 99 | if !Equal(out, tc.out) { 100 | t.Errorf("expecting %v got %v", tc.out, out) 101 | } 102 | } 103 | } 104 | 105 | func TestFilterFunc(t *testing.T) { 106 | tests := []struct { 107 | in []string 108 | f func(string) bool 109 | out []string 110 | }{ 111 | {in: slc, f: func(s string) bool { return s == "Lorem" }, 112 | out: []string{"Lorem", "Lorem"}}, 113 | {in: slc, f: func(s string) bool { return s == "srfrog" }, 114 | out: []string{}}, 115 | {in: nil, f: func(s string) bool { return s == "srfrog" }, 116 | out: []string{}}, 117 | {in: slc, f: nil, out: nil}, 118 | } 119 | 120 | for _, tc := range tests { 121 | out := FilterFunc(tc.in, tc.f) 122 | if !Equal(out, tc.out) { 123 | t.Errorf("expecting %v got %v", tc.out, out) 124 | } 125 | } 126 | } 127 | 128 | func TestCompare(t *testing.T) { 129 | tests := []struct { 130 | in []string 131 | out int 132 | }{ 133 | {in: []string{"Lorem"}, out: 0}, 134 | {in: []string{"Lorem", "ipsum"}, out: 0}, 135 | {in: []string{}, out: len(slc)}, 136 | {in: nil, out: len(slc)}, 137 | {in: []string{"Lorem", "ipsum", "bacon"}, out: -1}, 138 | {in: []string{"Lorem", "ipsum", "bacon", "ipsum"}, out: -2}, 139 | {in: []string{"Florem", "ipsum"}, out: -2}, 140 | } 141 | 142 | for _, tc := range tests { 143 | out := Compare(slc, tc.in) 144 | if out != tc.out { 145 | t.Errorf("expecting %v got %v", tc.out, out) 146 | } 147 | } 148 | } 149 | 150 | func TestReplace(t *testing.T) { 151 | a := []string{"Lorem", "", "Lorem"} 152 | tests := []struct { 153 | old, new string 154 | n int 155 | out []string 156 | }{ 157 | {old: "Lorem", new: "Florem", n: 1, 158 | out: []string{"Florem", "", "Lorem"}}, 159 | {old: "Lorem", new: "Florem", n: 0, 160 | out: []string{"Lorem", "", "Lorem"}}, 161 | {old: "Lorem", new: "Florem", n: 2, 162 | out: []string{"Florem", "", "Florem"}}, 163 | {old: "Lorem", new: "Florem", n: -1, 164 | out: []string{"Florem", "", "Florem"}}, 165 | {old: "Lorem", new: "Florem", n: 100, 166 | out: []string{"Florem", "", "Florem"}}, 167 | {old: "Lorem", new: "Florem", n: -100, 168 | out: []string{"Florem", "", "Florem"}}, 169 | {old: "Lorem", new: "Lorem", n: 1, 170 | out: []string{"Lorem", "", "Lorem"}}, 171 | {old: "", new: "Lorem", n: 1, 172 | out: []string{"Lorem", "Lorem", "Lorem"}}, 173 | } 174 | 175 | for _, tc := range tests { 176 | out := Replace(a, tc.old, tc.new, tc.n) 177 | if !Equal(tc.out, out) { 178 | t.Errorf("expecting %v got %v", tc.out, out) 179 | } 180 | } 181 | } 182 | 183 | func TestReverse(t *testing.T) { 184 | tests := []struct { 185 | in, out []string 186 | }{ 187 | {in: nil, out: nil}, 188 | {in: []string{"Lorem", "ipsum"}, 189 | out: []string{"ipsum", "Lorem"}}, 190 | {in: []string{"Lorem", "ipsum", "dolor", "sit", "amet"}, 191 | out: []string{"amet", "sit", "dolor", "ipsum", "Lorem"}}, 192 | } 193 | 194 | for _, tc := range tests { 195 | out := Reverse(tc.in) 196 | if !Equal(tc.out, out) { 197 | t.Errorf("expecting %v got %v", tc.out, out) 198 | } 199 | } 200 | } 201 | 202 | func TestSplit(t *testing.T) { 203 | type args struct { 204 | a []string 205 | sep string 206 | } 207 | tests := []struct { 208 | name string 209 | args args 210 | want [][]string 211 | }{ 212 | {name: "empty slice", 213 | args: args{a: []string{}, sep: ""}, want: [][]string{}}, 214 | {name: "nil", 215 | args: args{a: nil, sep: ""}, want: [][]string{}}, 216 | {name: "sep empty", 217 | args: args{a: []string{"1", "2", "3"}, sep: ""}, 218 | want: [][]string{{"1"}, {"2"}, {"3"}}}, 219 | {name: "mismatch", 220 | args: args{a: []string{"1", "2", "3"}, sep: "horse"}, 221 | want: [][]string{{"1", "2", "3"}}}, 222 | {name: "match", 223 | args: args{a: []string{"law", "and", "order"}, sep: "and"}, 224 | want: [][]string{{"law"}, {"order"}}}, 225 | } 226 | for _, tt := range tests { 227 | t.Run(tt.name, func(t *testing.T) { 228 | if got := Split(tt.args.a, tt.args.sep); !reflect.DeepEqual(got, tt.want) { 229 | t.Errorf("Split() = %v, want %v", got, tt.want) 230 | } 231 | }) 232 | } 233 | } 234 | 235 | func TestSplitN(t *testing.T) { 236 | type args struct { 237 | a []string 238 | sep string 239 | n int 240 | } 241 | tests := []struct { 242 | name string 243 | args args 244 | want [][]string 245 | }{ 246 | {name: "empty slice 0,-1", 247 | args: args{a: []string{}, sep: "", n: -1}, want: [][]string{}}, 248 | {name: "empty slice 0,0", 249 | args: args{a: []string{}, sep: "", n: 0}, want: nil}, 250 | {name: "empty slice 0,1", 251 | args: args{a: []string{}, sep: "", n: 1}, want: [][]string{}}, 252 | {name: "nil", 253 | args: args{a: nil, sep: "", n: 0}, want: nil}, 254 | {name: "sep empty", 255 | args: args{a: []string{"1", "2", "3"}, sep: "", n: 1}, 256 | want: [][]string{{"1"}, {"2"}, {"3"}}}, 257 | {name: "mismatch 0,-1", 258 | args: args{a: []string{"1", "2", "3"}, sep: "horse", n: -1}, 259 | want: [][]string{{"1", "2", "3"}}}, 260 | {name: "mismatch 0,1", 261 | args: args{a: []string{"1", "2", "3"}, sep: "horse", n: 1}, 262 | want: [][]string{{"1", "2", "3"}}}, 263 | {name: "match 1,1", 264 | args: args{a: []string{"law", "and", "order"}, sep: "and", n: 1}, 265 | want: [][]string{{"law"}, {"order"}}}, 266 | {name: "match 1,2", 267 | args: args{a: []string{"law", "and", "order"}, sep: "and", n: 2}, 268 | want: [][]string{{"law"}, {"order"}}}, 269 | {name: "match 3,1", 270 | args: args{a: []string{"Pig", "and", "Ale", "and", "Bar", "and", "Inn"}, sep: "and", n: 1}, 271 | want: [][]string{{"Pig"}, {"Ale", "and", "Bar", "and", "Inn"}}}, 272 | {name: "match 3,2", 273 | args: args{a: []string{"Pig", "and", "Ale", "and", "Bar", "and", "Inn"}, sep: "and", n: 2}, 274 | want: [][]string{{"Pig"}, {"Ale"}, {"Bar", "and", "Inn"}}}, 275 | {name: "match 3,3", 276 | args: args{a: []string{"Pig", "and", "Ale", "and", "Bar", "and", "Inn"}, sep: "and", n: 3}, 277 | want: [][]string{{"Pig"}, {"Ale"}, {"Bar"}, {"Inn"}}}, 278 | {name: "match 3,-1", 279 | args: args{a: []string{"Pig", "and", "Ale", "and", "Bar", "and", "Inn"}, sep: "and", n: -1}, 280 | want: [][]string{{"Pig"}, {"Ale"}, {"Bar"}, {"Inn"}}}, 281 | {name: "whys", 282 | args: args{a: []string{"why", "why", "why"}, sep: "why", n: 1}, 283 | want: [][]string{{}, {"why", "why"}}}, 284 | } 285 | for _, tt := range tests { 286 | t.Run(tt.name, func(t *testing.T) { 287 | if got := SplitN(tt.args.a, tt.args.sep, tt.args.n); !reflect.DeepEqual(got, tt.want) { 288 | t.Errorf("SplitN() = %v, want %v", got, tt.want) 289 | } 290 | }) 291 | } 292 | } 293 | 294 | func TestChunk(t *testing.T) { 295 | type args struct { 296 | a []string 297 | size int 298 | } 299 | tests := []struct { 300 | name string 301 | args args 302 | want [][]string 303 | }{ 304 | {name: "nil", args: args{a: nil, size: 0}, want: nil}, 305 | {name: "0,-1", args: args{a: []string{}, size: -1}, want: nil}, 306 | {name: "0,0", args: args{a: []string{}, size: 0}, want: nil}, 307 | {name: "0,1", args: args{a: []string{}, size: 1}, want: [][]string{}}, 308 | {name: "2,1", args: args{a: []string{"1", "2"}, size: 1}, want: [][]string{{"1"}, {"2"}}}, 309 | {name: "2,2", args: args{a: []string{"1", "2"}, size: 2}, want: [][]string{{"1", "2"}}}, 310 | {name: "7,1", 311 | args: args{a: []string{"1", "2", "3", "4", "5", "6", "7"}, size: 1}, 312 | want: [][]string{{"1"}, {"2"}, {"3"}, {"4"}, {"5"}, {"6"}, {"7"}}}, 313 | {name: "7,2", 314 | args: args{a: []string{"1", "2", "3", "4", "5", "6", "7"}, size: 2}, 315 | want: [][]string{{"1", "2"}, {"3", "4"}, {"5", "6"}, {"7"}}}, 316 | {name: "7,3", 317 | args: args{a: []string{"1", "2", "3", "4", "5", "6", "7"}, size: 3}, 318 | want: [][]string{{"1", "2", "3"}, {"4", "5", "6"}, {"7"}}}, 319 | {name: "7,4", 320 | args: args{a: []string{"1", "2", "3", "4", "5", "6", "7"}, size: 4}, 321 | want: [][]string{{"1", "2", "3", "4"}, {"5", "6", "7"}}}, 322 | {name: "7,5", 323 | args: args{a: []string{"1", "2", "3", "4", "5", "6", "7"}, size: 5}, 324 | want: [][]string{{"1", "2", "3", "4", "5"}, {"6", "7"}}}, 325 | {name: "7,6", 326 | args: args{a: []string{"1", "2", "3", "4", "5", "6", "7"}, size: 6}, 327 | want: [][]string{{"1", "2", "3", "4", "5", "6"}, {"7"}}}, 328 | {name: "7,7", 329 | args: args{a: []string{"1", "2", "3", "4", "5", "6", "7"}, size: 7}, 330 | want: [][]string{{"1", "2", "3", "4", "5", "6", "7"}}}, 331 | {name: "7,8", 332 | args: args{a: []string{"1", "2", "3", "4", "5", "6", "7"}, size: 8}, 333 | want: [][]string{{"1", "2", "3", "4", "5", "6", "7"}}}, 334 | } 335 | for _, tt := range tests { 336 | t.Run(tt.name, func(t *testing.T) { 337 | if got := Chunk(tt.args.a, tt.args.size); !reflect.DeepEqual(got, tt.want) { 338 | t.Errorf("Chunk() = %v, want %v", got, tt.want) 339 | } 340 | }) 341 | } 342 | } 343 | 344 | func TestIntersect(t *testing.T) { 345 | type args struct { 346 | a []string 347 | b []string 348 | } 349 | tests := []struct { 350 | name string 351 | args args 352 | want []string 353 | }{ 354 | {name: "nil", args: args{a: nil, b: nil}, want: nil}, 355 | {name: "none", args: args{a: []string{"1"}, b: []string{"2"}}, want: nil}, 356 | {name: "one", args: args{a: []string{"1"}, b: []string{"1"}}, want: []string{"1"}}, 357 | {name: "1,3", 358 | args: args{a: []string{"1", "2", "3"}, b: []string{"1", "4", "3"}}, 359 | want: []string{"1", "3"}}, 360 | {name: "3", 361 | args: args{a: []string{"1", "2", "3"}, b: []string{"3", "3", "3"}}, 362 | want: []string{"3"}}, 363 | {name: "3,3,3", 364 | args: args{a: []string{"3", "3", "3"}, b: []string{"1", "4", "3"}}, 365 | want: []string{"3", "3", "3"}}, 366 | } 367 | for _, tt := range tests { 368 | t.Run(tt.name, func(t *testing.T) { 369 | if got := Intersect(tt.args.a, tt.args.b); !reflect.DeepEqual(got, tt.want) { 370 | t.Errorf("Intersect() = %v, want %v", got, tt.want) 371 | } 372 | }) 373 | } 374 | } 375 | 376 | func TestMerge(t *testing.T) { 377 | type args struct { 378 | ss [][]string 379 | } 380 | tests := []struct { 381 | name string 382 | args args 383 | want []string 384 | }{ 385 | {name: "nil", args: args{ss: nil}, want: nil}, 386 | {name: "n=0", 387 | args: args{ss: [][]string{{""}}}, 388 | want: []string{""}}, 389 | {name: "n=1", 390 | args: args{ss: [][]string{{"1"}}}, 391 | want: []string{"1"}}, 392 | {name: "n=2", 393 | args: args{ss: [][]string{{"1"}, {"2", "2"}}}, 394 | want: []string{"1", "2", "2"}}, 395 | {name: "n=3", 396 | args: args{ss: [][]string{{"1"}, {"2", "2"}, {"3", "3", "3"}}}, 397 | want: []string{"1", "2", "2", "3", "3", "3"}}, 398 | } 399 | for _, tt := range tests { 400 | t.Run(tt.name, func(t *testing.T) { 401 | if got := Merge(tt.args.ss...); !reflect.DeepEqual(got, tt.want) { 402 | t.Errorf("Merge() = %v, want %v", got, tt.want) 403 | } 404 | }) 405 | } 406 | } 407 | 408 | func TestShuffle(t *testing.T) { 409 | type args struct { 410 | a []string 411 | } 412 | tests := []struct { 413 | name string 414 | args args 415 | want []string 416 | }{ 417 | {name: "nil", args: args{a: nil}, want: nil}, 418 | {name: "n=0", args: args{a: []string{}}, want: []string{}}, 419 | {name: "n=1", args: args{a: []string{"1"}}, want: []string{"1"}}, 420 | } 421 | for _, tt := range tests { 422 | t.Run(tt.name, func(t *testing.T) { 423 | if got := Shuffle(tt.args.a); !reflect.DeepEqual(got, tt.want) { 424 | t.Errorf("Shuffle() = %v, want %v", got, tt.want) 425 | } 426 | }) 427 | } 428 | } 429 | 430 | func TestInsertAt(t *testing.T) { 431 | type args struct { 432 | a []string 433 | idx int 434 | values []string 435 | } 436 | tests := []struct { 437 | name string 438 | args args 439 | want []string 440 | }{ 441 | {name: "nil", args: args{a: nil, idx: 0, values: nil}, want: nil}, 442 | {name: "empty", args: args{a: []string{}, idx: 0, values: []string{}}, want: []string{}}, 443 | {name: "a=0,v=1,idx=0", 444 | args: args{a: []string{}, idx: 0, values: []string{"1"}}, 445 | want: []string{"1"}}, 446 | {name: "a=3,v=1,idx=0", 447 | args: args{a: []string{"a", "b", "c"}, idx: 0, values: []string{"1"}}, 448 | want: []string{"1", "a", "b", "c"}}, 449 | {name: "a=3,v=1,idx=1", 450 | args: args{a: []string{"a", "b", "c"}, idx: 1, values: []string{"1"}}, 451 | want: []string{"a", "1", "b", "c"}}, 452 | {name: "a=3,v=1,idx=2", 453 | args: args{a: []string{"a", "b", "c"}, idx: 2, values: []string{"1"}}, 454 | want: []string{"a", "b", "1", "c"}}, 455 | {name: "a=3,v=1,idx=3", 456 | args: args{a: []string{"a", "b", "c"}, idx: 3, values: []string{"1"}}, 457 | want: []string{"a", "b", "c", "1"}}, 458 | } 459 | for _, tt := range tests { 460 | t.Run(tt.name, func(t *testing.T) { 461 | if got := InsertAt(tt.args.a, tt.args.idx, tt.args.values...); !reflect.DeepEqual(got, tt.want) { 462 | t.Errorf("InsertAt() = %v, want %v", got, tt.want) 463 | } 464 | }) 465 | } 466 | } 467 | 468 | func TestPop(t *testing.T) { 469 | slc := []string{"1", "2", "3"} 470 | 471 | type args struct { 472 | a *[]string 473 | } 474 | tests := []struct { 475 | name string 476 | args args 477 | want string 478 | }{ 479 | {name: "empty", args: args{a: &([]string{})}, want: ""}, 480 | {name: "pop-1", args: args{a: &slc}, want: "3"}, 481 | {name: "pop-2", args: args{a: &slc}, want: "2"}, 482 | {name: "pop-3", args: args{a: &slc}, want: "1"}, 483 | {name: "pop-4", args: args{a: &slc}, want: ""}, 484 | } 485 | for _, tt := range tests { 486 | t.Run(tt.name, func(t *testing.T) { 487 | if got := Pop(tt.args.a); got != tt.want { 488 | t.Errorf("Pop() = %v, want %v", got, tt.want) 489 | } 490 | }) 491 | } 492 | } 493 | 494 | func TestPush(t *testing.T) { 495 | var slc []string 496 | 497 | type args struct { 498 | a *[]string 499 | s []string 500 | } 501 | tests := []struct { 502 | name string 503 | args args 504 | want int 505 | }{ 506 | {name: "empty", args: args{a: &([]string{}), s: []string{}}, want: 0}, 507 | {name: "push-1", args: args{a: &slc, s: []string{"1"}}, want: 1}, 508 | } 509 | for _, tt := range tests { 510 | t.Run(tt.name, func(t *testing.T) { 511 | if got := Push(tt.args.a, tt.args.s...); got != tt.want { 512 | t.Errorf("Push() = %v, want %v", got, tt.want) 513 | } 514 | }) 515 | } 516 | } 517 | 518 | func TestEqual(t *testing.T) { 519 | type args struct { 520 | a []string 521 | b []string 522 | } 523 | tests := []struct { 524 | name string 525 | args args 526 | want bool 527 | }{ 528 | {name: "nil", args: args{a: nil, b: nil}, want: true}, 529 | {name: "equal", args: args{a: []string{"1"}, b: []string{"1"}}, want: true}, 530 | {name: "a-longer", args: args{a: []string{"1", "2"}, b: []string{"1"}}, want: false}, 531 | {name: "b-longer", args: args{a: []string{"1"}, b: []string{"1", "2"}}, want: false}, 532 | {name: "a-empty", args: args{a: []string{}, b: []string{"1", "2"}}, want: false}, 533 | } 534 | for _, tt := range tests { 535 | t.Run(tt.name, func(t *testing.T) { 536 | if got := Equal(tt.args.a, tt.args.b); got != tt.want { 537 | t.Errorf("Equal() = %v, want %v", got, tt.want) 538 | } 539 | }) 540 | } 541 | } 542 | 543 | func TestSlice(t *testing.T) { 544 | slc := []string{"a", "b", "c", "d", "e"} 545 | 546 | type args struct { 547 | a []string 548 | offset int 549 | length int 550 | } 551 | tests := []struct { 552 | name string 553 | args args 554 | want []string 555 | }{ 556 | {name: "nil", args: args{a: nil, offset: -1, length: 0}, want: nil}, 557 | {name: "a=1,offset=-1,length=0", 558 | args: args{a: []string{"1"}, offset: -1, length: 0}, 559 | want: []string{"1"}}, 560 | {name: "a=5,offset=2,length=0", 561 | args: args{a: slc, offset: 2, length: 0}, 562 | want: []string{"c", "d", "e"}}, 563 | {name: "a=5,offset=-2,length=1", 564 | args: args{a: slc, offset: -2, length: 1}, 565 | want: []string{"d"}}, 566 | {name: "a=5,offset=0,length=3", 567 | args: args{a: slc, offset: 0, length: 3}, 568 | want: []string{"a", "b", "c"}}, 569 | {name: "a=5,offset=1,length=2", 570 | args: args{a: slc, offset: 1, length: 2}, 571 | want: []string{"b", "c"}}, 572 | {name: "a=5,offset=-3,length=-4", 573 | args: args{a: slc, offset: -3, length: -4}, 574 | want: nil}, 575 | } 576 | // slc = []string{"Lorem", "ipsum", "dolor", "sit", "amet", "consectetur", "", "elit", "donec", "tempus", "Lorem"} 577 | for _, tt := range tests { 578 | t.Run(tt.name, func(t *testing.T) { 579 | if got := Slice(tt.args.a, tt.args.offset, tt.args.length); !reflect.DeepEqual(got, tt.want) { 580 | t.Errorf("Slice() = %v, want %v", got, tt.want) 581 | } 582 | }) 583 | } 584 | } 585 | 586 | func TestSplice(t *testing.T) { 587 | slc := []string{"a", "b", "c"} 588 | 589 | type args struct { 590 | a []string 591 | offset int 592 | length int 593 | b []string 594 | } 595 | tests := []struct { 596 | name string 597 | args args 598 | want []string 599 | }{ 600 | {name: "nil", args: args{a: nil, b: nil}, want: nil}, 601 | {name: "a=0,b=1,offset=1,length=0", 602 | args: args{a: nil, offset: 1, length: 0, b: []string{"1"}}, 603 | want: nil}, 604 | {name: "a=3,b=2,offset=-1,length=1", 605 | args: args{a: slc, offset: -1, length: 1, b: []string{"1", "2"}}, 606 | want: []string{"a", "b", "1", "2"}}, 607 | {name: "a=3,b=1,offset=1,length=max", 608 | args: args{a: slc, offset: 1, length: len(slc), b: []string{"1"}}, 609 | want: []string{"a", "1"}}, 610 | {name: "a=3,b=0,offset=1,length=1", 611 | args: args{a: slc, offset: 1, length: 1, b: nil}, 612 | want: []string{"a", "c"}}, 613 | {name: "a=3,b=0,offset=-1,length=1", 614 | args: args{a: slc, offset: -1, length: 1, b: nil}, 615 | want: []string{"a", "c"}}, 616 | {name: "a=10,b=3,offset=3,length=3", 617 | args: args{a: Repeat("x", 10), offset: 3, length: 3, b: []string{"1", "2", "3"}}, 618 | want: []string{"x", "x", "x", "1", "2", "3", "x", "x", "x", "x"}}, 619 | } 620 | for _, tt := range tests { 621 | t.Run(tt.name, func(t *testing.T) { 622 | if got := Splice(tt.args.a, tt.args.offset, tt.args.length, tt.args.b...); !reflect.DeepEqual(got, tt.want) { 623 | t.Errorf("Splice() = %v, want %v", got, tt.want) 624 | } 625 | }) 626 | } 627 | } 628 | 629 | func TestUnique(t *testing.T) { 630 | type args struct { 631 | a []string 632 | } 633 | tests := []struct { 634 | name string 635 | args args 636 | want []string 637 | }{ 638 | {name: "nil", args: args{a: nil}, want: nil}, 639 | {name: "diff=3", 640 | args: args{a: []string{"1", "2", "3"}}, 641 | want: []string{"1", "2", "3"}}, 642 | {name: "diff=2", 643 | args: args{a: []string{"1", "1", "3"}}, 644 | want: []string{"1", "3"}}, 645 | {name: "diff=1", 646 | args: args{a: []string{"1", "1", "1"}}, 647 | want: []string{"1"}}, 648 | } 649 | for _, tt := range tests { 650 | t.Run(tt.name, func(t *testing.T) { 651 | if got := Unique(tt.args.a); !reflect.DeepEqual(got, tt.want) { 652 | t.Errorf("Unique() = %v, want %v", got, tt.want) 653 | } 654 | }) 655 | } 656 | } 657 | 658 | func TestIndexAny(t *testing.T) { 659 | type args struct { 660 | a []string 661 | b []string 662 | } 663 | tests := []struct { 664 | name string 665 | args args 666 | want int 667 | }{ 668 | {name: "a=nil,b=nil", args: args{a: nil, b: nil}, want: -1}, 669 | {name: "a=nil,b=3", 670 | args: args{a: nil, b: []string{"1", "2", "3"}}, 671 | want: -1}, 672 | {name: "a=1,b=3,-1", 673 | args: args{a: []string{"x"}, b: []string{"1", "2", "3"}}, 674 | want: -1}, 675 | {name: "a=1,b=3,0", 676 | args: args{a: []string{"2"}, b: []string{"1", "2", "3"}}, 677 | want: 0}, 678 | {name: "a=3,b=3,0", 679 | args: args{a: []string{"3", "2", "1"}, b: []string{"1", "2", "3"}}, 680 | want: 0}, 681 | } 682 | for _, tt := range tests { 683 | t.Run(tt.name, func(t *testing.T) { 684 | if got := IndexAny(tt.args.a, tt.args.b); got != tt.want { 685 | t.Errorf("IndexAny() = %v, want %v", got, tt.want) 686 | } 687 | }) 688 | } 689 | } 690 | 691 | func TestLastIndexAny(t *testing.T) { 692 | type args struct { 693 | a []string 694 | b []string 695 | } 696 | tests := []struct { 697 | name string 698 | args args 699 | want int 700 | }{ 701 | {name: "a=nil,b=nil", args: args{a: nil, b: nil}, want: -1}, 702 | {name: "a=nil,b=3", 703 | args: args{a: nil, b: []string{"1", "2", "3"}}, 704 | want: -1}, 705 | {name: "a=1,b=3,-1", 706 | args: args{a: []string{"x"}, b: []string{"1", "2", "3"}}, 707 | want: -1}, 708 | {name: "a=1,b=3,0", 709 | args: args{a: []string{"2"}, b: []string{"1", "2", "3"}}, 710 | want: 0}, 711 | {name: "a=3,b=3,0", 712 | args: args{a: []string{"3", "2", "1"}, b: []string{"1", "2", "3"}}, 713 | want: 2}, 714 | } 715 | for _, tt := range tests { 716 | t.Run(tt.name, func(t *testing.T) { 717 | if got := LastIndexAny(tt.args.a, tt.args.b); got != tt.want { 718 | t.Errorf("LastIndexAny() = %v, want %v", got, tt.want) 719 | } 720 | }) 721 | } 722 | } 723 | 724 | func TestRepeat(t *testing.T) { 725 | type args struct { 726 | s string 727 | count int 728 | } 729 | tests := []struct { 730 | name string 731 | args args 732 | want []string 733 | }{ 734 | {name: "empty", args: args{s: "", count: 0}, want: []string{}}, 735 | {name: "x1", 736 | args: args{s: "x", count: 1}, 737 | want: []string{"x"}}, 738 | {name: "x5", 739 | args: args{s: "x", count: 5}, 740 | want: []string{"x", "x", "x", "x", "x"}}, 741 | } 742 | for _, tt := range tests { 743 | t.Run(tt.name, func(t *testing.T) { 744 | if got := Repeat(tt.args.s, tt.args.count); !reflect.DeepEqual(got, tt.want) { 745 | t.Errorf("Repeat() = %v, want %v", got, tt.want) 746 | } 747 | }) 748 | } 749 | } 750 | 751 | func TestSearch(t *testing.T) { 752 | type args struct { 753 | a []string 754 | substr string 755 | } 756 | tests := []struct { 757 | name string 758 | args args 759 | want int 760 | }{ 761 | {name: "nil", args: args{a: nil, substr: ""}, want: -1}, 762 | {name: "a=3,empty", 763 | args: args{a: []string{"a", "", "c"}, substr: ""}, 764 | want: 0}, 765 | {name: "a=3,space", 766 | args: args{a: []string{"a", " ", "c"}, substr: " "}, 767 | want: 1}, 768 | {name: "a=3,apple", 769 | args: args{a: []string{"orange", "quenepas", "crabapple", "kiwi"}, substr: "apple"}, 770 | want: 2}, 771 | } 772 | for _, tt := range tests { 773 | t.Run(tt.name, func(t *testing.T) { 774 | if got := Search(tt.args.a, tt.args.substr); got != tt.want { 775 | t.Errorf("Search() = %v, want %v", got, tt.want) 776 | } 777 | }) 778 | } 779 | } 780 | 781 | func TestReduce(t *testing.T) { 782 | type args struct { 783 | a []string 784 | f func(string, int, string) string 785 | } 786 | tests := []struct { 787 | name string 788 | args args 789 | want string 790 | }{ 791 | {name: "single", 792 | args: args{ 793 | a: []string{"1"}, 794 | f: func(acc string, i int, v string) string { return "" }}, 795 | want: "1"}, 796 | {name: "max", 797 | args: args{ 798 | a: []string{"a", "b", "a", "b", "c", "e", "e", "c", "d", "d", "d", "d"}, 799 | f: func(acc string, i int, v string) string { 800 | if v > acc { 801 | return v 802 | } 803 | return acc 804 | }}, 805 | want: "e"}, 806 | {name: "join", 807 | args: args{ 808 | a: []string{"1", "2", "3", "4"}, 809 | f: func(acc string, i int, v string) string { 810 | if acc != "" { 811 | acc = acc + ", " 812 | } 813 | return acc + v 814 | }}, 815 | want: "1, 2, 3, 4"}, 816 | } 817 | for _, tt := range tests { 818 | t.Run(tt.name, func(t *testing.T) { 819 | if got := Reduce(tt.args.a, tt.args.f); got != tt.want { 820 | t.Errorf("Reduce() = %v, want %v", got, tt.want) 821 | } 822 | }) 823 | } 824 | } 825 | --------------------------------------------------------------------------------