Type-safe functions for common Go slice operations.
go get github.com/psampaz/slice
âś” = Supported
âś• = Non supported
- = Not yet implemented
bool | byte | complex(all) | float(all) | int(all) | string | uint(all) | uintptr | |
---|---|---|---|---|---|---|---|---|
Batch | - | - | - | - | - | - | - | - |
Contains | âś” | âś” | âś” | âś” | âś” | âś” | âś” | âś” |
Copy | âś” | âś” | âś” | âś” | âś” | âś” | âś” | âś” |
Deduplicate | âś” | âś” | âś” | âś” | âś” | âś” | âś” | âś” |
Delete | âś” | âś” | âś” | âś” | âś” | âś” | âś” | âś” |
DeleteRange | âś” | âś” | âś” | âś” | âś” | âś” | âś” | âś” |
Filter | âś” | âś” | âś” | âś” | âś” | âś” | âś” | âś” |
Insert | - | - | - | - | - | - | - | - |
Max | âś• | âś” | âś• | âś” | âś” | âś• | âś” | âś” |
Min | âś• | âś” | âś• | âś” | âś” | âś• | âś” | âś” |
Pop | âś” | âś” | âś” | âś” | âś” | âś” | âś” | âś” |
Push | - | - | - | - | - | - | - | - |
Reverse | âś” | âś” | âś” | âś” | âś” | âś” | âś” | âś” |
Shift | - | - | - | - | - | - | - | - |
Shuffle | âś” | âś” | âś” | âś” | âś” | âś” | âś” | âś” |
Sum | âś• | âś” | âś” | âś” | âś” | âś• | âś” | âś” |
Unshift | - | - | - | - | - | - | - | - |
Deduplicate performs order preserving, in place deduplication of a slice
a := []int{1, 2, 3, 2, 5, 3}
a = slice.DeduplicateInt(a) // [1, 2, 3, 5]
Delete removes an element at a specific index of a slice. An error is return in case the index is out of bounds or the slice is nil or empty.
a := []int{1, 2, 3, 4, 5}
a, err = slice.DeleteInt(a, 2) // [1, 2, 4, 5], nil
DeleteRange deletes the elements between from and to index (inclusive) from a slice. An error is return in case the index is out of bounds or the slice is nil or empty.
a := []int{1, 2, 3, 4, 5}
a, err = slice.DeleteRangeInt(a, 2, 3) // [1, 2, 5], nil
Contains checks if a specific value exists in a slice.
a := []int{1, 2, 3, 4, 5}
exists := slice.ContainsInt(a, 3) // true
Copy creates a copy of a slice. The resulting slice has the same elements as the original but the underlying array is different. See https://github.com/go101/go101/wiki
a := []int{1, 2, 3, 4}
b := slice.CopyInt(a) // [1, 2, 3, 4]
Filter performs in place filtering of a slice based on a predicate
a := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
keep := func(x int) bool {
return x%2 == 0
}
a = slice.FilterInt(a, keep) // [2, 4, 6, 8, 10]
Max returns the maximum value of a slice or an error in case of a nil or empty slice.
a := []int{1, 2, 3, 0, 4, 5}
max, err := slice.MaxInt(a) // 5, nil
Min returns the minimum value of a slice or an error in case of a nil or empty slice.
a := []int{1, 2, 3, 0, 4, 5}
min, err := slice.MinInt(a) // 0, nil
Pop removes and returns the last value a slice and the remaining slice. An error is returned in case of a nil or empty slice.
a := []int{1, 2, 3, 4, 5}
v, a, err := slice.PopInt(a) // 5, [1, 2, 3, 4], nil
Reverse performs in place reversal of a slice
a := []int{1, 2, 3, 4, 5}
a = slice.ReverseInt(a) // [5, 4, 3, 2, 1]
Shuffle shuffles (in place) a slice
a := []int{1, 2, 3, 4, 5}
a = slice.ShuffleInt(a) // [3, 5, 1, 4, 2] (random output)
Sum returns the sum of the values of a slice or an error in case of a nil or empty slice
a := []int{1, 2, 3}
sum, err := slice.SumInt(a) // 6, nil
if you want to run the test suite for this library:
$ go test -v -cover
- SliceTricks (https://github.com/golang/go/wiki/SliceTricks). This was the inspiration behind this library.
- Genny (https://github.com/cheekybits/genny). In order to speedup the development and avoid massive copy paste, the excellent Genny library was used.
You are very welcome to contribute new operations or bug fixes in this library.
-
Use only functions. This is a function based library so struct based operations will not be accepted, in order to preserve simplicity and consistency.
-
If the operation is not working on a nil or empty slice, then the function should return an error.
-
If the operation accepts slice indexes as parameters, then the function should guard against out of bound index values and return an error in that case.
-
All operations should be in place operations, meaning that they should alter the original slice.
-
Each function should have precise documentation.
-
Each operation should live in each own file. Example:
min.go min_test.go
-
The naming convention for functions is OperationType. Example:
MinInt32()
instead of
Int32Min()
-
Implement ALL applicable types in the same PR.
-
Include one testable example for Int type at the end of the test file.
-
Include one example in the Examples section of README
-
Update the table in the Operation section of README
-
Update the UNRELEASED section of CHANGELOG
- All code should be 100% covered with tests
- All operations should be tested for 3 scenarios at least:
- nil slice
- empty slice
- non empty slice
golangci.com runs on all PRs. Code is checked with golint, go vet, gofmt, plus 20+ linters, and review comments will be automatically added in your PR in case of a failure. You can see the whole list of linters here: https://golangci.com/product#linters
- Open an issue describing the new operation, the proposed name and the applicable types.
- If the operation is approved to be included in the library, create a small PR the implementation and test for only only type.
- After code review you can proceed the implementation for the rest types. This is necessary because if you submit a PR with the implementation and test for all types, a small correction during review could eventually lead to a big refactor due to code duplication.
The following steps are an example of how to use https://github.com/cheekybits/genny to implement the min operation:
-
Install Genny
go get github.com/cheekybits/genny
-
Create a file named min_genny.go
package slice import ( "errors" "github.com/cheekybits/genny/generic" ) type Type generic.Type // MinType returns the minimum value of an Type slice or an error in case of a nil or empty slice func MinType(a []Type) (Type, error) { if len(a) == 0 { return 0, errors.New("Cannot get the minimum of a nil or empty slice") } min := a[0] for k := 1; k < len(a); k++ { if a[k] < min { min = a[k] } } return min, nil }
-
Use genny to generate code for all Go's built in types:
cat min_genny.go | genny gen Type=BUILTINS > min.go
This step will generate a file min.go with the following content:
package slice import "errors" // MinByte returns the minimum value of a byte slice or an error in case of a nil or empty slice func MinByte(a []byte) (byte, error) { if len(a) == 0 { return 0, errors.New("Cannot get the minimum of a nil or empty slice") } min := a[0] for k := 1; k < len(a); k++ { if a[k] < min { min = a[k] } } return min, nil } // MinFloat32 returns the minimum value of a float32 slice or an error in case of a nil or empty slice func MinFloat32(a []float32) (float32, error) { if len(a) == 0 { return 0, errors.New("Cannot get the minimum of a nil or empty slice") } min := a[0] for k := 1; k < len(a); k++ { if a[k] < min { min = a[k] } } return min, nil } . . . .
-
Delete the implementation for all types not applicable for the operation
-
Create a file named min_genny_test.go
package slice import ( "fmt" "testing" ) func TestMinType(t *testing.T) { type args struct { a []Type } tests := []struct { name string args args want Type wantErr bool }{ { name: "nil slice", args: args{ a: nil, }, want: 0, wantErr: true, }, { name: "empty slice", args: args{ a: []Type{}, }, want: 0, wantErr: true, }, { name: "non empty slice", args: args{ a: []Type{1, 3, 2, 0, 5, 4}, }, want: 0, wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := MinType(tt.args.a) if (err != nil) != tt.wantErr { t.Errorf("MinType() error = %v, wantErr %v", err, tt.wantErr) return } if got != tt.want { t.Errorf("MinType() = %v, want %v", got, tt.want) } }) } }
-
Use genny to generate tests for all Go's built in types:
cat min_genny_test.go | genny gen Type=BUILTINS > min_test.go
This step will generate a file min_test.go with tests for each one of Go's built in types.
-
Remove tests for non applicable types.
-
Adjust the tests for each one of the types.
-
Delete min_genny.go and min_genny_test.go