├── .github └── workflows │ └── test.yml ├── .gitignore ├── LICENSE ├── README.md ├── doc.go ├── go.mod ├── slices.go └── slices_test.go /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | 16 | - name: Set up Go 17 | uses: actions/setup-go@v2 18 | with: 19 | go-version: 1.18 20 | 21 | - name: Run tests 22 | run: go test -v -race -coverprofile=coverage.txt -covermode=atomic ./... 23 | - name: Upload coverage to Codecov 24 | uses: codecov/codecov-action@v2 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | coverage.txt 2 | codecov 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2022, Ibrahim Serdar Acikgoz 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![build](https://img.shields.io/github/workflow/status/isacikgoz/slices/Test) ![goversion](https://img.shields.io/github/go-mod/go-version/isacikgoz/slices) [![Go Reference](https://pkg.go.dev/badge/github.com/isacikgoz/slices.svg)](https://pkg.go.dev/github.com/isacikgoz/slices) ![coverage](https://img.shields.io/codecov/c/github/isacikgoz/slices) 2 | 3 | # Slices 4 | 5 | `slices` package provides some utility functions on Go slice types. It leverages the generic types hence requires Go 1.18 and above. 6 | 7 | ## Download 8 | 9 | You can simply run `go get github.com/isacikgoz/slices` to start using in your own code. 10 | 11 | ## Examples 12 | 13 | Delete with preserving the order of the slice 14 | 15 | ```Go 16 | s := []int{0, 1, 2, 3} 17 | s = slices.Delete(s, 0) // removes the first element from the slice 18 | ``` 19 | 20 | Insert another slice from an index 21 | 22 | ```Go 23 | s1 := []int{0, 3, 4, 5} 24 | s2 := []int{1, 2} 25 | s1 = slices.Insert(s1, 1, s2...) // will have (0, 1, 2, 3, 4, 5) 26 | ``` 27 | 28 | Filter slice 29 | ```Go 30 | s1 := []string{ 31 | "http://foo.com", 32 | "https://bar.com", 33 | "https://example.net", 34 | "http://go.org", 35 | } 36 | 37 | s2 := Filter(s1, func(v string) bool { 38 | return strings.HasPrefix(v, "https://") 39 | }) 40 | 41 | // s2 will be {"https://bar.com", "https://example.net"} 42 | ``` 43 | 44 | ## License 45 | 46 | [BSD-3-Clause](/LICENSE) 47 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Package slices provides utility functions for manipulating slices. 2 | 3 | package slices 4 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/isacikgoz/slices 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /slices.go: -------------------------------------------------------------------------------- 1 | package slices 2 | 3 | import ( 4 | "math/rand" 5 | ) 6 | 7 | // Delete removes the i. element from s. 8 | func Delete[T any](s []T, i int) []T { 9 | if i > len(s)-1 { 10 | return s 11 | } 12 | 13 | var e T 14 | copy(s[i:], s[i+1:]) 15 | s[len(s)-1] = e 16 | s = s[:len(s)-1] 17 | 18 | return s 19 | } 20 | 21 | // Insert inserts a T or a slice of T into s from i. index. 22 | func Insert[T any](s []T, i int, e ...T) []T { 23 | if i > len(s)-1 && len(s) != 0 { 24 | i = len(s) - 1 25 | } else if len(s) == 0 { 26 | i = 0 27 | } else if i < 0 { 28 | s = append(e, s...) 29 | } 30 | 31 | s = append(s[:i], append(e, s[i:]...)...) 32 | return s 33 | } 34 | 35 | // Duplicate copies slice s into another slice. 36 | func Duplicate[T any](s []T) []T { 37 | dup := make([]T, len(s)) 38 | copy(dup, s) 39 | 40 | return dup 41 | } 42 | 43 | // Push adds an element at the end of s. 44 | func Push[T any](s []T, e T) []T { 45 | return append(s, e) 46 | } 47 | 48 | // Pop returns the last element from s. Returns zero value of T of s is empty. 49 | func Pop[T any](s []T) (T, []T) { 50 | if len(s) == 0 { 51 | var e T 52 | return e, s 53 | } 54 | 55 | return s[len(s)-1], s[:len(s)-1] 56 | } 57 | 58 | // Shuffle pseudo-randomizes the order of elements of the s. 59 | func Shuffle[T any](s []T) []T { 60 | rand.Shuffle(len(s), func(i, j int) { 61 | s[i], s[j] = s[j], s[i] 62 | }) 63 | 64 | return s 65 | } 66 | 67 | // Reverse reverses the order of s. 68 | func Reverse[T any](s []T) []T { 69 | for i := len(s)/2 - 1; i >= 0; i-- { 70 | opp := len(s) - 1 - i 71 | s[i], s[opp] = s[opp], s[i] 72 | } 73 | 74 | return s 75 | } 76 | 77 | // Unique removes duplicate values in a slice 78 | func Unique[T comparable](src []T) []T { 79 | var result []T 80 | var elemMap = make(map[T]struct{}) 81 | for i := range src { 82 | if _, ok := elemMap[src[i]]; !ok { 83 | result = append(result, src[i]) 84 | elemMap[src[i]] = struct{}{} 85 | } 86 | } 87 | 88 | return result 89 | } 90 | 91 | // Filter filters the elements of s according to the boolean value of the predicate. 92 | func Filter[T any](s []T, f func(T) bool) []T { 93 | var n []T 94 | for _, e := range s { 95 | if f(e) { 96 | n = append(n, e) 97 | } 98 | } 99 | 100 | return n 101 | } 102 | 103 | // Diff returns the values from src that are not present in other slices 104 | func Diff[T comparable](src []T, arrays ...[]T) []T { 105 | var elemMap = make(map[T]struct{}) 106 | for _, array := range arrays { 107 | for i := range array { 108 | elemMap[array[i]] = struct{}{} 109 | } 110 | } 111 | var result = make([]T, 0, len(src)) 112 | for i := range src { 113 | if _, ok := elemMap[src[i]]; !ok { 114 | result = append(result, src[i]) 115 | } 116 | } 117 | return result 118 | } 119 | -------------------------------------------------------------------------------- /slices_test.go: -------------------------------------------------------------------------------- 1 | package slices 2 | 3 | import ( 4 | "math/rand" 5 | "reflect" 6 | "strings" 7 | "testing" 8 | ) 9 | 10 | func TestDelete(t *testing.T) { 11 | t.Run("Delete from slice", func(t *testing.T) { 12 | s := []int{0, 1, 2, 3} 13 | s = Delete(s, 0) 14 | if len(s) != 3 { 15 | t.Fatalf("could not delete data") 16 | } 17 | if s[0] != 1 { 18 | t.Fatalf("could not delete data") 19 | } 20 | }) 21 | 22 | t.Run("Delete out of index", func(t *testing.T) { 23 | defer func() { 24 | if r := recover(); r != nil { 25 | t.Fatalf("should've not panic") 26 | } 27 | }() 28 | 29 | s := []int{0, 1, 2, 3} 30 | s = Delete(s, 4) 31 | if len(s) != 4 { 32 | t.Fatalf("could not delete data") 33 | } 34 | if s[0] != 0 { 35 | t.Fatalf("could not delete data") 36 | } 37 | }) 38 | 39 | t.Run("On empty slice", func(t *testing.T) { 40 | defer func() { 41 | if r := recover(); r != nil { 42 | t.Fatalf("should've not panic") 43 | } 44 | }() 45 | 46 | var s []struct{} 47 | _ = Delete(s, 0) 48 | }) 49 | } 50 | 51 | func TestInsert(t *testing.T) { 52 | t.Run("Insert another slice from beginning", func(t *testing.T) { 53 | s1 := []int{0, 1, 2, 3} 54 | s2 := []int{4, 5} 55 | s1 = Insert(s1, 0, s2...) 56 | if len(s1) != 6 { 57 | t.Fatalf("should've insert slice") 58 | } 59 | if s1[1] != 5 { 60 | t.Fatalf("should've insert slice correctly") 61 | } 62 | }) 63 | 64 | t.Run("Insert another slice from out of index", func(t *testing.T) { 65 | defer func() { 66 | if r := recover(); r != nil { 67 | t.Fatalf("should've not panic") 68 | } 69 | }() 70 | 71 | s1 := []int{0, 1, 2, 3} 72 | s2 := []int{4, 5} 73 | s1 = Insert(s1, 5, s2...) 74 | if len(s1) != 6 { 75 | t.Fatalf("should've insert slice") 76 | } 77 | if s1[3] != 4 { 78 | t.Fatalf("should've insert slice correctly") 79 | } 80 | }) 81 | 82 | t.Run("On empty slice", func(t *testing.T) { 83 | defer func() { 84 | if r := recover(); r != nil { 85 | t.Fatalf("should've not panic") 86 | } 87 | }() 88 | 89 | var s1 []int 90 | s2 := []int{4, 5} 91 | s1 = Insert(s1, 5, s2...) 92 | if len(s1) != 2 { 93 | t.Fatalf("should've insert slice") 94 | } 95 | if s1[1] != 5 { 96 | t.Fatalf("should've insert slice correctly") 97 | } 98 | }) 99 | } 100 | 101 | func TestDuplicate(t *testing.T) { 102 | t.Run("Duplicate a slice", func(t *testing.T) { 103 | defer func() { 104 | if r := recover(); r != nil { 105 | t.Fatalf("should've not panic") 106 | } 107 | }() 108 | 109 | s1 := []int{0, 1, 2, 3} 110 | s2 := Duplicate(s1) 111 | if len(s1) != len(s2) { 112 | t.Fatalf("should've duplicate slice") 113 | } 114 | for i, e := range s1 { 115 | if s2[i] != e { 116 | t.Fatalf("should've duplicated in correct order") 117 | } 118 | } 119 | }) 120 | 121 | t.Run("Duplicate an empty slice", func(t *testing.T) { 122 | var s1 []int 123 | s2 := Duplicate(s1) 124 | if len(s1) != len(s2) { 125 | t.Fatalf("should've duplicate slice") 126 | } 127 | }) 128 | } 129 | 130 | func TestPush(t *testing.T) { 131 | t.Run("Push to empty slice", func(t *testing.T) { 132 | var s1 []int 133 | s1 = Push(s1, 1) 134 | if len(s1) != 1 { 135 | t.Fatalf("should've pushed element into the slice") 136 | } 137 | }) 138 | 139 | t.Run("Push to a slice", func(t *testing.T) { 140 | s1 := []int{0} 141 | s1 = Push(s1, 1) 142 | if len(s1) != 2 { 143 | t.Fatalf("should've pushed element into the slice") 144 | } 145 | }) 146 | } 147 | 148 | func TestPop(t *testing.T) { 149 | t.Run("Pop from a slice", func(t *testing.T) { 150 | s1 := []int{0} 151 | var e int 152 | e, s1 = Pop(s1) 153 | if len(s1) != 0 { 154 | t.Fatalf("should've popped element from the slice") 155 | } 156 | if e != 0 { 157 | t.Fatalf("should've popped correct element from the slice") 158 | } 159 | }) 160 | 161 | t.Run("Pop from an empty slice", func(t *testing.T) { 162 | s1 := []int{} 163 | var e int 164 | e, s1 = Pop(s1) 165 | if len(s1) != 0 { 166 | t.Fatalf("should've popped element from the slice") 167 | } 168 | if e != 0 { 169 | t.Fatalf("should've popped zero value of the element from the slice") 170 | } 171 | }) 172 | } 173 | 174 | func TestShuffle(t *testing.T) { 175 | t.Run("Shuffle an empty slice", func(t *testing.T) { 176 | s1 := []int{} 177 | s1 = Shuffle(s1) 178 | }) 179 | 180 | rand.Seed(42) // get default Source to a deterministic state. 181 | 182 | t.Run("Shuffle a slice", func(t *testing.T) { 183 | s1 := []int{0, 1} 184 | s2 := Duplicate(s1) 185 | s1 = Shuffle(s1) 186 | if len(s1) != 2 { 187 | t.Fatalf("should've shuffled slice, not changing its length") 188 | } 189 | 190 | if s1[0] != s2[1] || s1[1] != s2[0] { 191 | t.Fatalf("should've shuffled") 192 | } 193 | }) 194 | } 195 | 196 | func TestReverse(t *testing.T) { 197 | t.Run("Reverse an empty slice", func(t *testing.T) { 198 | defer func() { 199 | if r := recover(); r != nil { 200 | t.Fatalf("should've not panic") 201 | } 202 | }() 203 | 204 | s1 := []int{} 205 | s1 = Reverse(s1) 206 | }) 207 | 208 | t.Run("Reverse a slice", func(t *testing.T) { 209 | s1 := []int{0, 1} 210 | s2 := Duplicate(s1) 211 | s1 = Reverse(s1) 212 | if len(s1) != 2 { 213 | t.Fatalf("should've shuffled slice, not changing its length") 214 | } 215 | 216 | if s1[0] != s2[1] || s1[1] != s2[0] { 217 | t.Fatalf("should've shuffled") 218 | } 219 | }) 220 | } 221 | 222 | func TestFilter(t *testing.T) { 223 | t.Run("Filter an empty slice", func(t *testing.T) { 224 | defer func() { 225 | if r := recover(); r != nil { 226 | t.Fatalf("should've not panic") 227 | } 228 | }() 229 | 230 | s1 := []int{} 231 | _ = Filter(s1, func(v int) bool { 232 | return v%2 == 0 233 | }) 234 | }) 235 | 236 | t.Run("Filter a string slice", func(t *testing.T) { 237 | s1 := []string{ 238 | "http://foo.com", 239 | "https://bar.com", 240 | "https://example.net", 241 | "http://go.org", 242 | } 243 | 244 | s2 := Filter(s1, func(v string) bool { 245 | return strings.HasPrefix(v, "https://") 246 | }) 247 | 248 | want := []string{"https://bar.com", "https://example.net"} 249 | 250 | if !reflect.DeepEqual(s2, want) { 251 | t.Fatalf("should've filtered") 252 | } 253 | }) 254 | 255 | t.Run("Filter an int slice", func(t *testing.T) { 256 | s1 := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} 257 | 258 | s2 := Filter(s1, func(v int) bool { 259 | return v%2 == 0 260 | }) 261 | 262 | want := []int{0, 2, 4, 6, 8} 263 | 264 | if !reflect.DeepEqual(s2, want) { 265 | t.Fatalf("should've filtered") 266 | } 267 | }) 268 | } 269 | 270 | func TestUnique(t *testing.T) { 271 | t.Run("make unique an empty slice", func(t *testing.T) { 272 | defer func() { 273 | if r := recover(); r != nil { 274 | t.Fatalf("should've not panic") 275 | } 276 | }() 277 | 278 | var s1 []int 279 | _ = Unique(s1) 280 | }) 281 | 282 | t.Run("make unique", func(t *testing.T) { 283 | defer func() { 284 | if r := recover(); r != nil { 285 | t.Fatalf("should've not panic") 286 | } 287 | }() 288 | s1 := []int{1, 2, 2, 3, 4, 2, 4} 289 | s2 := []int{1, 2, 3, 4} 290 | s1 = Unique(s1) 291 | if len(s1) != len(s2) { 292 | t.Fatalf("should've to be the same length") 293 | } 294 | for i := range s1 { 295 | if s1[i] != s2[i] { 296 | t.Fatalf("should've to be the same") 297 | } 298 | } 299 | }) 300 | } 301 | 302 | func TestDiff(t *testing.T) { 303 | t.Run("run with an empty slice", func(t *testing.T) { 304 | defer func() { 305 | if r := recover(); r != nil { 306 | t.Fatalf("should've not panic") 307 | } 308 | }() 309 | 310 | var s1 []int 311 | s1 = Diff(s1, s1) 312 | if len(s1) != 0 { 313 | t.Fatalf("should've to be empty") 314 | } 315 | }) 316 | 317 | t.Run("get diff", func(t *testing.T) { 318 | defer func() { 319 | if r := recover(); r != nil { 320 | t.Fatalf("should've not panic") 321 | } 322 | }() 323 | 324 | var s1 = []int{1, 2, 3} 325 | var s2 = []int{3, 4} 326 | // sh 327 | 328 | if !reflect.DeepEqual(Diff(s1, s2), []int{1, 2}) { 329 | t.Fatalf("should've to be the same") 330 | } 331 | 332 | var s3 = []int{2, 3} 333 | if !reflect.DeepEqual(Diff(s1, s2, s3), []int{1}) { 334 | t.Fatalf("should've to be the same") 335 | } 336 | 337 | var s4 = []int{1} 338 | if !reflect.DeepEqual(Diff(s1, s2, s3, s4), []int{}) { 339 | t.Fatalf("should've to be the same") 340 | } 341 | }) 342 | } 343 | --------------------------------------------------------------------------------