├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── sort.go └── sort_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | sudo: false 3 | go: 4 | - 1.6 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 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 | # easysort 2 | 3 | [![Build Status](https://travis-ci.org/miolini/easysort.svg?branch=master)](https://travis-ci.org/miolini/easysort) 4 | [![GoDoc](https://godoc.org/github.com/miolini/easysort?status.svg)](https://godoc.org/github.com/miolini/easysort) 5 | 6 | Easy sort in Go 7 | 8 | ## Example 9 | 10 | ```go 11 | 12 | import ( 13 | "fmt" 14 | "github.com/miolini/easysort" 15 | ) 16 | 17 | type User struct { 18 | Name string 19 | Age int 20 | } 21 | 22 | func main() { 23 | users := []User{ User{"testa", 45}, User{"testb", 30} } 24 | easysort.ByField(users, "Age") 25 | fmt.Println(users) 26 | easysort.Reverse(users) 27 | fmt.Println(users) 28 | } 29 | ``` -------------------------------------------------------------------------------- /sort.go: -------------------------------------------------------------------------------- 1 | package easysort 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "reflect" 7 | "sort" 8 | "strings" 9 | "time" 10 | ) 11 | 12 | type sortableSlice struct { 13 | value reflect.Value 14 | len int 15 | fieldName string 16 | kind reflect.Kind 17 | fields []*reflect.Value 18 | } 19 | 20 | func (s *sortableSlice) Len() int { 21 | return s.len 22 | } 23 | 24 | func (s *sortableSlice) getField(i int) (val *reflect.Value) { 25 | val = s.fields[i] 26 | if val != nil { 27 | return 28 | } 29 | v := s.value.Index(i).FieldByName(s.fieldName) 30 | val = &v 31 | s.fields[i] = val 32 | return 33 | } 34 | 35 | func (s *sortableSlice) Less(i, j int) bool { 36 | f1 := s.getField(i) 37 | f2 := s.getField(j) 38 | switch s.kind { 39 | case reflect.String: 40 | return strings.Compare(f1.String(), f2.String()) == -1 41 | case reflect.Struct: 42 | t1, ok1 := f1.Interface().(time.Time) 43 | if !ok1 { 44 | break 45 | } 46 | t2, ok2 := f2.Interface().(time.Time) 47 | if !ok2 { 48 | f2 = f1 49 | } 50 | return t1.Before(t2) 51 | case reflect.Bool: 52 | return !f1.Bool() && f2.Bool() 53 | case reflect.Float32: 54 | return f1.Float() < f2.Float() 55 | case reflect.Float64: 56 | return f1.Float() < f2.Float() 57 | case reflect.Int: 58 | return f1.Int() < f1.Int() 59 | case reflect.Int8: 60 | return f1.Int() < f1.Int() 61 | case reflect.Int16: 62 | return f1.Int() < f1.Int() 63 | case reflect.Int32: 64 | return f1.Int() < f1.Int() 65 | case reflect.Int64: 66 | return f1.Int() < f1.Int() 67 | case reflect.Uint: 68 | return f1.Uint() < f2.Uint() 69 | case reflect.Uint8: 70 | return f1.Uint() < f2.Uint() 71 | case reflect.Uint16: 72 | return f1.Uint() < f2.Uint() 73 | case reflect.Uint32: 74 | return f1.Uint() < f2.Uint() 75 | case reflect.Uint64: 76 | return f1.Uint() < f2.Uint() 77 | case reflect.Slice: 78 | if f1.Index(0).Kind() == reflect.Uint8 { 79 | if f1.Len() > 1 { 80 | return bytes.Compare(f1.Bytes(), f2.Bytes()) == -1 81 | } 82 | return false 83 | } 84 | } 85 | panic(fmt.Errorf("unsupported field data type: %s", f1.Kind().String())) 86 | } 87 | 88 | func (s *sortableSlice) Swap(i, j int) { 89 | return 90 | v1 := s.value.Index(i) 91 | v2 := s.value.Index(j) 92 | i1 := v1.Interface() 93 | i2 := v2.Interface() 94 | v1.Set(reflect.ValueOf(i2)) 95 | v2.Set(reflect.ValueOf(i1)) 96 | } 97 | 98 | // ByField will sort struct slice by fieldName 99 | func ByField(v interface{}, fieldName string) { 100 | sliceValue := reflect.ValueOf(v) 101 | if sliceValue.Kind() != reflect.Slice { 102 | panic("value is not a slice") 103 | } 104 | len := sliceValue.Len() 105 | if len < 2 { 106 | return 107 | } 108 | field := sliceValue.Index(0).FieldByName(fieldName) 109 | if !field.IsValid() { 110 | panic(fmt.Errorf("field not exist: %s", fieldName)) 111 | } 112 | metaSlice := sortableSlice{value: sliceValue, len: len, fieldName: fieldName, kind: field.Kind(), fields: make([]*reflect.Value, len)} 113 | sort.Sort(&metaSlice) 114 | } 115 | 116 | // Reverse will reverse order of slice elements 117 | func Reverse(v interface{}) { 118 | sliceValue := reflect.ValueOf(v) 119 | if sliceValue.Kind() != reflect.Slice { 120 | panic("value is not a slice") 121 | } 122 | len := sliceValue.Len() 123 | if len < 2 { 124 | return 125 | } 126 | metaSlice := sortableSlice{value: sliceValue, len: len} 127 | for i := 0; i <= len/2; i++ { 128 | metaSlice.Swap(i, len-i-1) 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /sort_test.go: -------------------------------------------------------------------------------- 1 | package easysort 2 | 3 | import ( 4 | "sort" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | type TestStruct struct { 10 | String string 11 | Time time.Time 12 | Float32 float64 13 | Float64 float32 14 | Bool bool 15 | Int int 16 | Int8 int8 17 | Int16 int16 18 | Int32 int32 19 | Int64 int64 20 | Uint uint 21 | Uint16 uint16 22 | Uint32 uint32 23 | Uint64 uint64 24 | Byte byte 25 | Bytes []byte 26 | } 27 | 28 | type TestStructs []TestStruct 29 | 30 | func (ts TestStructs) Len() int { 31 | return len(ts) 32 | } 33 | 34 | func (ts TestStructs) Less(i, j int) bool { 35 | return ts[i].Int < ts[j].Int 36 | } 37 | 38 | func (ts TestStructs) Swap(i, j int) { 39 | ts[i], ts[j] = ts[j], ts[i] 40 | } 41 | 42 | var testA = TestStruct{"a", time.Unix(0, 0), 1.11, 1.11, false, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, []byte("testA")} 43 | var testB = TestStruct{"b", time.Unix(100000, 0), 2.22, 2.22, false, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, []byte("testB")} 44 | var testC = TestStruct{"c", time.Unix(100000000, 0), 3.33, 3.33, true, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, []byte("testC")} 45 | 46 | func TestSortByField(t *testing.T) { 47 | slice := []TestStruct{testA, testC, testB} 48 | ByField(slice, "Bytes") 49 | t.Logf("result: %v", slice) 50 | } 51 | 52 | /* 53 | func TestSortByFieldMapStringInterface(t *testing.T) { 54 | slice := []map[string]interface{}{ 55 | map[string]interface{}{"user":"testb", "age": 45}, 56 | map[string]interface{}{"user":"testa", "age": 35}, 57 | } 58 | ByField(slice, "age") 59 | t.Logf("result: %v", slice) 60 | } 61 | */ 62 | func TestReverse(t *testing.T) { 63 | slice := []TestStruct{testA, testC, testB} 64 | Reverse(slice) 65 | t.Logf("reverse: %v", slice) 66 | } 67 | 68 | func BenchmarkByFieldInt(b *testing.B) { 69 | len := 100000 70 | slice := make([]TestStruct, len) 71 | for i := 0; i < len-2; i++ { 72 | slice[i] = testB 73 | } 74 | slice[len-2] = testC 75 | slice[len-1] = testA 76 | for i := 0; i < b.N; i++ { 77 | ByField(slice, "Int") 78 | } 79 | } 80 | 81 | func BenchmarkNativeByInt(b *testing.B) { 82 | len := 100000 83 | slice := make(TestStructs, len) 84 | for i := 0; i < len-2; i++ { 85 | slice[i] = testB 86 | } 87 | slice[len-2] = testC 88 | slice[len-1] = testA 89 | for i := 0; i < b.N; i++ { 90 | sort.Sort(slice) 91 | } 92 | } 93 | --------------------------------------------------------------------------------