diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..43c8706 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,28 @@ +language: go +go: + - "1.13.x" + - "1.14.x" + - "1.x" + +env: + - GO111MODULE=on GOFLAGS=-mod=readonly GOPROXY=https://proxy.golang.org + +cache: + directories: + - $GOPATH/pkg/mod + +before_install: + - curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $GOPATH/bin + - go mod download + - go get github.com/mattn/goveralls + +script: + - golangci-lint run ./... + - go vet ./... + - go test -covermode=count -coverprofile=profile.cov ./... + - goveralls -coverprofile=profile.cov -service=travis-ci + +notifications: + email: + on_success: never + on_failure: always \ No newline at end of file diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..f3e9129 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,76 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at nick@wiersma.co.za. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq diff --git a/LICENCE b/LICENCE new file mode 100644 index 0000000..597a1a4 --- /dev/null +++ b/LICENCE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Nicholas Wiersma + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a182b7b --- /dev/null +++ b/Makefile @@ -0,0 +1 @@ +include github.com/hamba/make/golang diff --git a/README.md b/README.md new file mode 100644 index 0000000..b88d15a --- /dev/null +++ b/README.md @@ -0,0 +1,75 @@ +![Logo](http://svg.wiersma.co.za/hamba/project?title=slices&tag=Slice%20helper%20functions) + +[![Go Report Card](https://goreportcard.com/badge/github.com/hamba/slices)](https://goreportcard.com/report/github.com/hamba/slices) +[![Build Status](https://travis-ci.com/hamba/slices.svg?branch=master)](https://travis-ci.com/hamba/slices) +[![Coverage Status](https://coveralls.io/repos/github/hamba/slices/badge.svg?branch=master)](https://coveralls.io/github/hamba/slices?branch=master) +[![GoDoc](https://godoc.org/github.com/hamba/slices?status.svg)](https://godoc.org/github.com/hamba/slices) +[![GitHub release](https://img.shields.io/github/release/hamba/slices.svg)](https://github.com/hamba/slices/releases) +[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/hamba/slices/master/LICENSE) + +A fast Go slices codec + +## Overview + +Install with: + +```shell +go get github.com/hamba/slices +``` + +## Usage + +### Contains + +Contains is a generic slice contains function. + +Supports: bool, string, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64 + +```go +slice := []string{"foo", "bar"} +v := "bar" + +fmt.Println(slices.Contains(slice, v)) +// Outputs: true +``` + +### GreaterOf + +GreaterOf returns a greater function for the given slice type for slice sorting. + +Supports: bool, string, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64 + +```go +slice := []string{"foo", "bar"} +sort.Slice(slice, slices.GreaterOf(slice)) + +fmt.Println(slice) +// Outputs: [foo bar] +``` + +### LesserOf + +LesserOf returns a lesser function for the given slice type for slice sorting. + +Supports: bool, string, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64 + +```go +slice := []string{"foo", "bar"} +sort.Slice(slice, slices.LesserOf(slice)) + +fmt.Println(slice) +// Outputs: [bar foo] +``` + +## Benchmark + +``` +BenchmarkContains-8 36659943 30.0 ns/op 0 B/op 0 allocs/op +BenchmarkContainsNative-8 48539482 24.9 ns/op 0 B/op 0 allocs/op +BenchmarkGreaterOf-8 6257299 193 ns/op 80 B/op 3 allocs/op +BenchmarkGreaterOfNative-8 7963461 149 ns/op 64 B/op 2 allocs/op +BenchmarkLesserOf-8 6122317 192 ns/op 80 B/op 3 allocs/op +BenchmarkLesserOfNative-8 7947242 152 ns/op 64 B/op 2 allocs/op +``` + +Always benchmark with your own workload. The result depends heavily on the data input. diff --git a/contains.go b/contains.go new file mode 100644 index 0000000..a827480 --- /dev/null +++ b/contains.go @@ -0,0 +1,210 @@ +package slices + +import ( + "unsafe" +) + +type containsFn func(sptr, vptr unsafe.Pointer) bool + +// Contains determines if the slice contains val. +func Contains(slice, val interface{}) bool { + fn, ok := containsOf(slice, val) + if fn == nil { + panic("slice is not supported slice type") + } + if !ok { + panic("val is not the same type as slice") + } + + sptr := noescape(ptrOf(slice)) + vptr := noescape(ptrOf(val)) + return fn(sptr, vptr) +} + +func containsOf(slice, val interface{}) (containsFn, bool) { + switch slice.(type) { + case []bool: + _, ok := val.(bool) + return boolContains, ok + case []string: + _, ok := val.(string) + return stringContains, ok + case []int: + _, ok := val.(int) + return intContains, ok + case []int8: + _, ok := val.(int8) + return int8Contains, ok + case []int16: + _, ok := val.(int16) + return int16Contains, ok + case []int32: + _, ok := val.(int32) + return int32Contains, ok + case []int64: + _, ok := val.(int64) + return int64Contains, ok + case []uint: + _, ok := val.(uint) + return uintContains, ok + case []uint8: + _, ok := val.(uint8) + return uint8Contains, ok + case []uint16: + _, ok := val.(uint16) + return uint16Contains, ok + case []uint32: + _, ok := val.(uint32) + return uint32Contains, ok + case []uint64: + _, ok := val.(uint64) + return uint64Contains, ok + case []float32: + _, ok := val.(float32) + return float32Contains, ok + case []float64: + _, ok := val.(float64) + return float64Contains, ok + } + return nil, false +} + +func boolContains(sptr, vptr unsafe.Pointer) bool { + v := *(*bool)(vptr) + for _, vv := range *(*[]bool)(sptr) { + if vv == v { + return true + } + } + return false +} + +func stringContains(sptr, vptr unsafe.Pointer) bool { + v := *(*string)(vptr) + for _, vv := range *(*[]string)(sptr) { + if vv == v { + return true + } + } + return false +} + +func intContains(sptr, vptr unsafe.Pointer) bool { + v := *(*int)(vptr) + for _, vv := range *(*[]int)(sptr) { + if vv == v { + return true + } + } + return false +} + +func int8Contains(sptr, vptr unsafe.Pointer) bool { + v := *(*int8)(vptr) + for _, vv := range *(*[]int8)(sptr) { + if vv == v { + return true + } + } + return false +} + +func int16Contains(sptr, vptr unsafe.Pointer) bool { + v := *(*int16)(vptr) + for _, vv := range *(*[]int16)(sptr) { + if vv == v { + return true + } + } + return false +} + +func int32Contains(sptr, vptr unsafe.Pointer) bool { + v := *(*int32)(vptr) + for _, vv := range *(*[]int32)(sptr) { + if vv == v { + return true + } + } + return false +} + +func int64Contains(sptr, vptr unsafe.Pointer) bool { + v := *(*int64)(vptr) + for _, vv := range *(*[]int64)(sptr) { + if vv == v { + return true + } + } + return false +} + +func uintContains(sptr, vptr unsafe.Pointer) bool { + v := *(*uint)(vptr) + for _, vv := range *(*[]uint)(sptr) { + if vv == v { + return true + } + } + return false +} + +func uint8Contains(sptr, vptr unsafe.Pointer) bool { + v := *(*uint8)(vptr) + for _, vv := range *(*[]uint8)(sptr) { + if vv == v { + return true + } + } + return false +} + +func uint16Contains(sptr, vptr unsafe.Pointer) bool { + v := *(*uint16)(vptr) + for _, vv := range *(*[]uint16)(sptr) { + if vv == v { + return true + } + } + return false +} + +func uint32Contains(sptr, vptr unsafe.Pointer) bool { + v := *(*uint32)(vptr) + for _, vv := range *(*[]uint32)(sptr) { + if vv == v { + return true + } + } + return false +} + +func uint64Contains(sptr, vptr unsafe.Pointer) bool { + v := *(*uint64)(vptr) + for _, vv := range *(*[]uint64)(sptr) { + if vv == v { + return true + } + } + return false +} + +func float32Contains(sptr, vptr unsafe.Pointer) bool { + v := *(*float32)(vptr) + for _, vv := range *(*[]float32)(sptr) { + if vv == v { + return true + } + } + return false +} + +func float64Contains(sptr, vptr unsafe.Pointer) bool { + v := *(*float64)(vptr) + for _, vv := range *(*[]float64)(sptr) { + if vv == v { + return true + } + } + return false +} diff --git a/contains_test.go b/contains_test.go new file mode 100644 index 0000000..60f121f --- /dev/null +++ b/contains_test.go @@ -0,0 +1,237 @@ +package slices_test + +import ( + "testing" + + "github.com/hamba/slices" + "github.com/stretchr/testify/assert" +) + +func TestContains(t *testing.T) { + tests := []struct { + name string + slice interface{} + val interface{} + want bool + }{ + { + name: "bool contains", + slice: []bool{false, false, true}, + val: true, + want: true, + }, + { + name: "bool not contains", + slice: []bool{false, false, false}, + val: true, + want: false, + }, + { + name: "string contains", + slice: []string{"foo", "bar", "baz", "bat"}, + val: "bat", + want: true, + }, + { + name: "string not contains", + slice: []string{"foo", "bar", "baz", "bat"}, + val: "test", + want: false, + }, + { + name: "int contains", + slice: []int{1, 2, 3, 4}, + val: 4, + want: true, + }, + { + name: "int not contains", + slice: []int{1, 2, 3, 4}, + val: 5, + want: false, + }, + { + name: "int8 contains", + slice: []int8{1, 2, 3, 4}, + val: int8(4), + want: true, + }, + { + name: "int8 not contains", + slice: []int8{1, 2, 3, 4}, + val: int8(5), + want: false, + }, + { + name: "int16 contains", + slice: []int16{1, 2, 3, 4}, + val: int16(4), + want: true, + }, + { + name: "int16 not contains", + slice: []int16{1, 2, 3, 4}, + val: int16(5), + want: false, + }, + { + name: "int32 contains", + slice: []int32{1, 2, 3, 4}, + val: int32(4), + want: true, + }, + { + name: "int32 not contains", + slice: []int32{1, 2, 3, 4}, + val: int32(5), + want: false, + }, + { + name: "int64 contains", + slice: []int64{1, 2, 3, 4}, + val: int64(4), + want: true, + }, + { + name: "int64 not contains", + slice: []int64{1, 2, 3, 4}, + val: int64(5), + want: false, + }, + { + name: "uint contains", + slice: []uint{1, 2, 3, 4}, + val: uint(4), + want: true, + }, + { + name: "uint not contains", + slice: []uint{1, 2, 3, 4}, + val: uint(5), + want: false, + }, + { + name: "uint8 contains", + slice: []uint8{1, 2, 3, 4}, + val: uint8(4), + want: true, + }, + { + name: "uint8 not contains", + slice: []uint8{1, 2, 3, 4}, + val: uint8(5), + want: false, + }, + { + name: "uint16 contains", + slice: []uint16{1, 2, 3, 4}, + val: uint16(4), + want: true, + }, + { + name: "uint16 not contains", + slice: []uint16{1, 2, 3, 4}, + val: uint16(5), + want: false, + }, + { + name: "uint32 contains", + slice: []uint32{1, 2, 3, 4}, + val: uint32(4), + want: true, + }, + { + name: "uint32 not contains", + slice: []uint32{1, 2, 3, 4}, + val: uint32(5), + want: false, + }, + { + name: "uint64 contains", + slice: []uint64{1, 2, 3, 4}, + val: uint64(4), + want: true, + }, + { + name: "uint64 not contains", + slice: []uint64{1, 2, 3, 4}, + val: uint64(5), + want: false, + }, + { + name: "float32 contains", + slice: []float32{1, 2, 3, 4}, + val: float32(4), + want: true, + }, + { + name: "float32 not contains", + slice: []float32{1, 2, 3, 4}, + val: float32(5), + want: false, + }, + { + name: "float64 contains", + slice: []float64{1, 2, 3, 4}, + val: float64(4), + want: true, + }, + { + name: "float64 not contains", + slice: []float64{1, 2, 3, 4}, + val: float64(5), + want: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := slices.Contains(tt.slice, tt.val) + + assert.Equal(t, tt.want, got) + }) + } +} + +func TestContains_ChecksSliceType(t *testing.T) { + assert.Panics(t, func() { + slices.Contains("test", "test") + }) +} + +func TestContains_ChecksValType(t *testing.T) { + assert.Panics(t, func() { + slices.Contains([]string{"test"}, 1) + }) +} + +func BenchmarkContains(b *testing.B) { + slice := []string{"foo", "bar", "baz", "bat"} + val := "bat" + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + slices.Contains(slice, val) + } +} + +func contains(slice []string, val string) bool { + for _, vv := range slice { + if vv == val { + return true + } + } + return false +} + +func BenchmarkContainsNative(b *testing.B) { + slice := []string{"foo", "bar", "baz", "bat"} + val := "bat" + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + contains(slice, val) + } +} diff --git a/example_test.go b/example_test.go new file mode 100644 index 0000000..b6604ce --- /dev/null +++ b/example_test.go @@ -0,0 +1,32 @@ +package slices_test + +import ( + "fmt" + "sort" + + "github.com/hamba/slices" +) + +func ExampleContains() { + slice := []string{"foo", "bar"} + v := "bar" + + fmt.Println(slices.Contains(slice, v)) + // Output: true +} + +func ExampleGreaterOf() { + slice := []string{"foo", "bar", "baz"} + sort.Slice(slice, slices.GreaterOf(slice)) + + fmt.Println(slice) + // Outputs: [bar baz foo] +} + +func ExampleLesserOf() { + slice := []string{"foo", "bar", "baz"} + sort.Slice(slice, slices.LesserOf(slice)) + + fmt.Println(slice) + // Outputs: [foo baz bar] +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..724d51c --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module github.com/hamba/slices + +go 1.13 + +require github.com/stretchr/testify v1.6.1 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..afe7890 --- /dev/null +++ b/go.sum @@ -0,0 +1,11 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/greater.go b/greater.go new file mode 100644 index 0000000..d317584 --- /dev/null +++ b/greater.go @@ -0,0 +1,141 @@ +package slices + +import ( + "unsafe" +) + +type greaterFn func(unsafe.Pointer) func(i, j int) bool + +// GreaterOf returns a greater function for slice sorting. +func GreaterOf(slice interface{}) func(i, j int) bool { + fn := greaterOf(slice) + if fn == nil { + panic("slice is not supported slice type") + } + + ptr := noescape(ptrOf(slice)) + return fn(ptr) +} + +func greaterOf(slice interface{}) greaterFn { + switch slice.(type) { + case []string: + return stringGreater + case []int: + return intGreater + case []int8: + return int8Greater + case []int16: + return int16Greater + case []int32: + return int32Greater + case []int64: + return int64Greater + case []uint: + return uintGreater + case []uint8: + return uint8Greater + case []uint16: + return uint16Greater + case []uint32: + return uint32Greater + case []uint64: + return uint64Greater + case []float32: + return float32Greater + case []float64: + return float64Greater + } + return nil +} + +func stringGreater(ptr unsafe.Pointer) func(i, j int) bool { + return func(i, j int) bool { + v := *(*[]string)(ptr) + return v[i] > v[j] + } +} + +func intGreater(ptr unsafe.Pointer) func(i, j int) bool { + return func(i, j int) bool { + v := *(*[]int)(ptr) + return v[i] > v[j] + } +} + +func int8Greater(ptr unsafe.Pointer) func(i, j int) bool { + return func(i, j int) bool { + v := *(*[]int8)(ptr) + return v[i] > v[j] + } +} + +func int16Greater(ptr unsafe.Pointer) func(i, j int) bool { + return func(i, j int) bool { + v := *(*[]int16)(ptr) + return v[i] > v[j] + } +} + +func int32Greater(ptr unsafe.Pointer) func(i, j int) bool { + return func(i, j int) bool { + v := *(*[]int32)(ptr) + return v[i] > v[j] + } +} + +func int64Greater(ptr unsafe.Pointer) func(i, j int) bool { + return func(i, j int) bool { + v := *(*[]int64)(ptr) + return v[i] > v[j] + } +} + +func uintGreater(ptr unsafe.Pointer) func(i, j int) bool { + return func(i, j int) bool { + v := *(*[]uint)(ptr) + return v[i] > v[j] + } +} + +func uint8Greater(ptr unsafe.Pointer) func(i, j int) bool { + return func(i, j int) bool { + v := *(*[]uint8)(ptr) + return v[i] > v[j] + } +} + +func uint16Greater(ptr unsafe.Pointer) func(i, j int) bool { + return func(i, j int) bool { + v := *(*[]uint16)(ptr) + return v[i] > v[j] + } +} + +func uint32Greater(ptr unsafe.Pointer) func(i, j int) bool { + return func(i, j int) bool { + v := *(*[]uint32)(ptr) + return v[i] > v[j] + } +} + +func uint64Greater(ptr unsafe.Pointer) func(i, j int) bool { + return func(i, j int) bool { + v := *(*[]uint64)(ptr) + return v[i] > v[j] + } +} + +func float32Greater(ptr unsafe.Pointer) func(i, j int) bool { + return func(i, j int) bool { + v := *(*[]float32)(ptr) + return v[i] > v[j] + } +} + +func float64Greater(ptr unsafe.Pointer) func(i, j int) bool { + return func(i, j int) bool { + v := *(*[]float64)(ptr) + return v[i] > v[j] + } +} diff --git a/greater_test.go b/greater_test.go new file mode 100644 index 0000000..586ecd2 --- /dev/null +++ b/greater_test.go @@ -0,0 +1,120 @@ +package slices_test + +import ( + "sort" + "testing" + + "github.com/hamba/slices" + "github.com/stretchr/testify/assert" +) + +func TestGreaterOf(t *testing.T) { + tests := []struct { + name string + slice interface{} + want interface{} + }{ + { + name: "string", + slice: []string{"foo", "bar", "baz", "bat"}, + want: []string{"foo", "baz", "bat", "bar"}, + }, + { + name: "int", + slice: []int{4, 2, 1, 3}, + want: []int{4, 3, 2, 1}, + }, + { + name: "int8", + slice: []int8{4, 2, 1, 3}, + want: []int8{4, 3, 2, 1}, + }, + { + name: "int16", + slice: []int16{4, 2, 1, 3}, + want: []int16{4, 3, 2, 1}, + }, + { + name: "int32", + slice: []int32{4, 2, 1, 3}, + want: []int32{4, 3, 2, 1}, + }, + { + name: "int64", + slice: []int64{4, 2, 1, 3}, + want: []int64{4, 3, 2, 1}, + }, + { + name: "uint", + slice: []uint{4, 2, 1, 3}, + want: []uint{4, 3, 2, 1}, + }, + { + name: "uint8", + slice: []uint8{4, 2, 1, 3}, + want: []uint8{4, 3, 2, 1}, + }, + { + name: "uint16", + slice: []uint16{4, 2, 1, 3}, + want: []uint16{4, 3, 2, 1}, + }, + { + name: "uint32", + slice: []uint32{4, 2, 1, 3}, + want: []uint32{4, 3, 2, 1}, + }, + { + name: "uint64", + slice: []uint64{4, 2, 1, 3}, + want: []uint64{4, 3, 2, 1}, + }, + { + name: "float32", + slice: []float32{4, 2, 1, 3}, + want: []float32{4, 3, 2, 1}, + }, + { + name: "float64", + slice: []float64{4, 2, 1, 3}, + want: []float64{4, 3, 2, 1}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + sort.Slice(tt.slice, slices.GreaterOf(tt.slice)) + + assert.Equal(t, tt.want, tt.slice) + }) + } +} + +func TestGreaterOf_ChecksSliceType(t *testing.T) { + assert.Panics(t, func() { + slices.GreaterOf("test") + }) +} + +func BenchmarkGreaterOf(b *testing.B) { + slice := []string{"foo", "bar", "baz", "bat"} + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + sort.Slice(slice, slices.GreaterOf(slice)) + } +} + +func BenchmarkGreaterOfNative(b *testing.B) { + slice := []string{"foo", "bar", "baz", "bat"} + greaterOf := func(i, j int) bool { + return slice[i] > slice[j] + } + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + sort.Slice(slice, greaterOf) + } +} diff --git a/lesser.go b/lesser.go new file mode 100644 index 0000000..2749795 --- /dev/null +++ b/lesser.go @@ -0,0 +1,141 @@ +package slices + +import ( + "unsafe" +) + +type lesserFn func(unsafe.Pointer) func(i, j int) bool + +// LesserOf returns a lesser function for slice sorting. +func LesserOf(slice interface{}) func(i, j int) bool { + fn := lesserOf(slice) + if fn == nil { + panic("slice is not supported slice type") + } + + ptr := noescape(ptrOf(slice)) + return fn(ptr) +} + +func lesserOf(slice interface{}) lesserFn { + switch slice.(type) { + case []string: + return stringLesser + case []int: + return intLesser + case []int8: + return int8Lesser + case []int16: + return int16Lesser + case []int32: + return int32Lesser + case []int64: + return int64Lesser + case []uint: + return uintLesser + case []uint8: + return uint8Lesser + case []uint16: + return uint16Lesser + case []uint32: + return uint32Lesser + case []uint64: + return uint64Lesser + case []float32: + return float32Lesser + case []float64: + return float64Lesser + } + return nil +} + +func stringLesser(ptr unsafe.Pointer) func(i, j int) bool { + return func(i, j int) bool { + v := *(*[]string)(ptr) + return v[i] < v[j] + } +} + +func intLesser(ptr unsafe.Pointer) func(i, j int) bool { + return func(i, j int) bool { + v := *(*[]int)(ptr) + return v[i] < v[j] + } +} + +func int8Lesser(ptr unsafe.Pointer) func(i, j int) bool { + return func(i, j int) bool { + v := *(*[]int8)(ptr) + return v[i] < v[j] + } +} + +func int16Lesser(ptr unsafe.Pointer) func(i, j int) bool { + return func(i, j int) bool { + v := *(*[]int16)(ptr) + return v[i] < v[j] + } +} + +func int32Lesser(ptr unsafe.Pointer) func(i, j int) bool { + return func(i, j int) bool { + v := *(*[]int32)(ptr) + return v[i] < v[j] + } +} + +func int64Lesser(ptr unsafe.Pointer) func(i, j int) bool { + return func(i, j int) bool { + v := *(*[]int64)(ptr) + return v[i] < v[j] + } +} + +func uintLesser(ptr unsafe.Pointer) func(i, j int) bool { + return func(i, j int) bool { + v := *(*[]uint)(ptr) + return v[i] < v[j] + } +} + +func uint8Lesser(ptr unsafe.Pointer) func(i, j int) bool { + return func(i, j int) bool { + v := *(*[]uint8)(ptr) + return v[i] < v[j] + } +} + +func uint16Lesser(ptr unsafe.Pointer) func(i, j int) bool { + return func(i, j int) bool { + v := *(*[]uint16)(ptr) + return v[i] < v[j] + } +} + +func uint32Lesser(ptr unsafe.Pointer) func(i, j int) bool { + return func(i, j int) bool { + v := *(*[]uint32)(ptr) + return v[i] < v[j] + } +} + +func uint64Lesser(ptr unsafe.Pointer) func(i, j int) bool { + return func(i, j int) bool { + v := *(*[]uint64)(ptr) + return v[i] < v[j] + } +} + +func float32Lesser(ptr unsafe.Pointer) func(i, j int) bool { + return func(i, j int) bool { + v := *(*[]float32)(ptr) + return v[i] < v[j] + } +} + +func float64Lesser(ptr unsafe.Pointer) func(i, j int) bool { + return func(i, j int) bool { + v := *(*[]float64)(ptr) + return v[i] < v[j] + } +} diff --git a/lesser_test.go b/lesser_test.go new file mode 100644 index 0000000..072e49f --- /dev/null +++ b/lesser_test.go @@ -0,0 +1,120 @@ +package slices_test + +import ( + "sort" + "testing" + + "github.com/hamba/slices" + "github.com/stretchr/testify/assert" +) + +func TestLesserOf(t *testing.T) { + tests := []struct { + name string + slice interface{} + want interface{} + }{ + { + name: "string", + slice: []string{"foo", "bar", "baz", "bat"}, + want: []string{"bar", "bat", "baz", "foo"}, + }, + { + name: "int", + slice: []int{4, 2, 1, 3}, + want: []int{1, 2, 3, 4}, + }, + { + name: "int8", + slice: []int8{4, 2, 1, 3}, + want: []int8{1, 2, 3, 4}, + }, + { + name: "int16", + slice: []int16{4, 2, 1, 3}, + want: []int16{1, 2, 3, 4}, + }, + { + name: "int32", + slice: []int32{4, 2, 1, 3}, + want: []int32{1, 2, 3, 4}, + }, + { + name: "int64", + slice: []int64{4, 2, 1, 3}, + want: []int64{1, 2, 3, 4}, + }, + { + name: "uint", + slice: []uint{4, 2, 1, 3}, + want: []uint{1, 2, 3, 4}, + }, + { + name: "uint8", + slice: []uint8{4, 2, 1, 3}, + want: []uint8{1, 2, 3, 4}, + }, + { + name: "uint16", + slice: []uint16{4, 2, 1, 3}, + want: []uint16{1, 2, 3, 4}, + }, + { + name: "uint32", + slice: []uint32{4, 2, 1, 3}, + want: []uint32{1, 2, 3, 4}, + }, + { + name: "uint64", + slice: []uint64{4, 2, 1, 3}, + want: []uint64{1, 2, 3, 4}, + }, + { + name: "float32", + slice: []float32{4, 2, 1, 3}, + want: []float32{1, 2, 3, 4}, + }, + { + name: "float64", + slice: []float64{4, 2, 1, 3}, + want: []float64{1, 2, 3, 4}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + sort.Slice(tt.slice, slices.LesserOf(tt.slice)) + + assert.Equal(t, tt.want, tt.slice) + }) + } +} + +func TestLesserOf_ChecksSliceType(t *testing.T) { + assert.Panics(t, func() { + slices.LesserOf("test") + }) +} + +func BenchmarkLesserOf(b *testing.B) { + slice := []string{"foo", "bar", "baz", "bat"} + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + sort.Slice(slice, slices.LesserOf(slice)) + } +} + +func BenchmarkLesserOfNative(b *testing.B) { + slice := []string{"foo", "bar", "baz", "bat"} + lesserOf := func(i, j int) bool { + return slice[i] < slice[j] + } + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + sort.Slice(slice, lesserOf) + } +} diff --git a/ptr.go b/ptr.go new file mode 100644 index 0000000..2b87223 --- /dev/null +++ b/ptr.go @@ -0,0 +1,16 @@ +package slices + +import "unsafe" + +type eface struct { + _ unsafe.Pointer + data unsafe.Pointer +} + +func ptrOf(obj interface{}) unsafe.Pointer { + return (*eface)(unsafe.Pointer(&obj)).data +} + +//go:linkname noescape runtime.noescape +//go:noescape +func noescape(p unsafe.Pointer) unsafe.Pointer diff --git a/ptr.s b/ptr.s new file mode 100644 index 0000000..e69de29