├── LICENSE ├── intersort.go ├── README.md └── intersort_test.go /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 Luke Champine 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /intersort.go: -------------------------------------------------------------------------------- 1 | package intersort // import "lukechampine.com/intersort" 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "sort" 7 | ) 8 | 9 | func less(x, y interface{}) bool { 10 | justX := fmt.Sprint(map[interface{}]struct{}{x: struct{}{}}) 11 | justY := fmt.Sprint(map[interface{}]struct{}{y: struct{}{}}) 12 | return fmt.Sprint(map[interface{}]struct{}{ 13 | x: struct{}{}, 14 | y: struct{}{}, 15 | }) == fmt.Sprintf("map[%v %v]", justX[4:len(justX)-1], justY[4:len(justY)-1]) 16 | } 17 | 18 | // Slice implements sort.Interface for arbitrary objects, according to the map 19 | // ordering of the fmt package. 20 | type Slice []interface{} 21 | 22 | func (is Slice) Len() int { return len(is) } 23 | func (is Slice) Swap(i, j int) { is[i], is[j] = is[j], is[i] } 24 | func (is Slice) Less(i, j int) bool { return less(is[i], is[j]) } 25 | 26 | // Sort sorts arbitrary objects according to the map ordering of the fmt 27 | // package. 28 | func Sort(slice interface{}) { 29 | val := reflect.ValueOf(slice) 30 | if val.Type().Kind() != reflect.Slice { 31 | panic("intersort: cannot sort non-slice type") 32 | } 33 | sort.Slice(slice, func(i, j int) bool { 34 | return less(val.Index(i).Interface(), val.Index(j).Interface()) 35 | }) 36 | } 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | intersort 2 | --------- 3 | 4 | `intersort` sorts slices of arbitrary objects according to the rules of Go's 5 | `fmtsort` package. `fmtsort` is an internal package and thus cannot be imported 6 | directly; however, its behavior is exposed via the `fmt` package when printing 7 | maps. So we can sort arbitrary objects by sticking them in a map, printing it, 8 | and parsing the result. 9 | 10 | In other words, this package is an abomination and should not be used for 11 | anything. May the Go maintainers have mercy on my soul. 12 | 13 | **NOTE: This package requires Go 1.12.1.** 14 | 15 | ## Examples: 16 | 17 | ```go 18 | ints := []int{3, 1, 2} 19 | intersort.Sort(ints) // [1, 2, 3] 20 | 21 | strs := []string{"b", "c", "a"} 22 | intersort.Sort(strs) // [a, b, c] 23 | 24 | type Point struct { 25 | X, Y int 26 | } 27 | points := []Point{{2, 1}, {1, 1}, {1, 0}} 28 | intersort.Sort(points) // [{1, 0}, {1, 1}, {2, 1}] 29 | ``` 30 | 31 | You can even sort *differing* types! 32 | 33 | ```go 34 | objs := []interface{}{3, true, 1, "wat", http.DefaultClient, false} 35 | sort.Sort(intersort.Slice(objs)) // [false, true, 1, 3, wat, &{ 0s}] 36 | ``` 37 | 38 | However, the results of this may vary, and in general are unpredictable; see 39 | https://github.com/golang/go/issues/30398. 40 | 41 | 42 | ## Advance praise for intersort: 43 | 44 | > I tend to think that exposing the comparison function would be an attractive nuisance. - Ian Lance Taylor 45 | 46 | > It was a deliberate decision to keep this implementation private. - Rob Pike 47 | 48 | > Sorting [arbitrary objects] is not even possible in the general case. - cznic 49 | 50 | > This should not be done. - Rob Pike 51 | -------------------------------------------------------------------------------- /intersort_test.go: -------------------------------------------------------------------------------- 1 | package intersort 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "math/rand" 7 | "sort" 8 | "strings" 9 | "testing" 10 | ) 11 | 12 | func TestSingleType(t *testing.T) { 13 | ptrArray := [2]int{2, 1} 14 | chanArray := [2]chan int{make(chan int), make(chan int)} 15 | 16 | tests := []struct { 17 | desc string 18 | orig interface{} 19 | sorted interface{} 20 | }{ 21 | { 22 | // "When applicable, nil compares low" 23 | desc: "Nil", 24 | orig: []*int{&ptrArray[0], (*int)(nil)}, 25 | sorted: []*int{nil, &ptrArray[0]}, 26 | }, 27 | { 28 | // "ints, floats, and strings order by <" 29 | desc: "Ints", 30 | orig: []int{3, 2, 1}, 31 | sorted: []int{1, 2, 3}, 32 | }, 33 | { 34 | // "ints, floats, and strings order by <" 35 | desc: "Floats", 36 | orig: []float64{3.1, 2.1, 1.1}, 37 | sorted: []float64{1.1, 2.1, 3.1}, 38 | }, 39 | { 40 | // "ints, floats, and strings order by <" 41 | desc: "Strings", 42 | orig: []string{"c", "b", "a"}, 43 | sorted: []string{"a", "b", "c"}, 44 | }, 45 | { 46 | // "NaN compares less than non-NaN floats" 47 | desc: "NaN", 48 | orig: []float64{3.1, 2.1, math.NaN()}, 49 | sorted: []float64{math.NaN(), 2.1, 3.1}, 50 | }, 51 | { 52 | // "bool compares false before true" 53 | desc: "Bool", 54 | orig: []bool{true, false}, 55 | sorted: []bool{false, true}, 56 | }, 57 | { 58 | // "Complex compares real, then imaginary" 59 | desc: "Complex", 60 | orig: []complex128{2 + 1i, 1 + 2i}, 61 | sorted: []complex128{1 + 2i, 2 + 1i}, 62 | }, 63 | { 64 | // "Pointers compare by machine address" 65 | desc: "Pointers", 66 | orig: []*int{&ptrArray[1], &ptrArray[0]}, 67 | sorted: []*int{&ptrArray[0], &ptrArray[1]}, 68 | }, 69 | { 70 | // "Channel values compare by machine address" 71 | desc: "Channel", 72 | orig: []chan int{chanArray[1], chanArray[0]}, 73 | sorted: []chan int{chanArray[0], chanArray[1]}, 74 | }, 75 | { 76 | // "Structs compare each field in turn" 77 | desc: "Structs", 78 | orig: []struct{ x, y int }{{1, 0}, {0, 1}}, 79 | sorted: []struct{ x, y int }{{0, 1}, {1, 0}}, 80 | }, 81 | { 82 | // "Arrays compare each element in turn" 83 | desc: "Arrays", 84 | orig: [][2]int{{1, 0}, {0, 1}}, 85 | sorted: [][2]int{{0, 1}, {1, 0}}, 86 | }, 87 | } 88 | for _, test := range tests { 89 | t.Run(test.desc, func(t *testing.T) { 90 | Sort(test.orig) 91 | // can't use reflect.DeepEqual, since NaN != NaN 92 | if fmt.Sprint(test.orig) != fmt.Sprint(test.sorted) { 93 | t.Fatalf("expected %#v, got %#v", test.sorted, test.orig) 94 | } 95 | }) 96 | } 97 | } 98 | 99 | // "Interface values compare first by reflect.Type describing the 100 | // concrete type and then by concrete value as described in the 101 | // previous rules." 102 | func TestMultiType(t *testing.T) { 103 | ptrArray := [2]int{2, 1} 104 | chanArray := [2]chan int{make(chan int), make(chan int)} 105 | 106 | // Unfortunately, sorting is unpredictable because it compares the machine 107 | // addresses of the *reflect.rtype pointers. But we can at least assert that 108 | // the result contains all sorted subgroups. 109 | groups := [][]interface{}{ 110 | {(*int)(nil), &ptrArray[0], &ptrArray[1]}, 111 | {1, 2, 3}, 112 | {"a", "b", "c"}, 113 | {false, true}, 114 | {1 + 2i, 2 + 1i}, 115 | {chanArray[0], chanArray[1]}, 116 | {struct{ x, y int }{0, 1}, struct{ x, y int }{1, 0}}, 117 | {[2]int{0, 1}, [2]int{1, 0}}, 118 | } 119 | 120 | var elems Slice 121 | for _, g := range groups { 122 | elems = append(elems, g...) 123 | } 124 | rand.Shuffle(len(elems), elems.Swap) // nice 125 | 126 | sort.Sort(elems) 127 | str := fmt.Sprint(elems) 128 | for _, g := range groups { 129 | exp := strings.TrimSpace(fmt.Sprintln(g...)) 130 | if !strings.Contains(str, exp) { 131 | t.Errorf("sorted map should contain %q", exp) 132 | } 133 | } 134 | } 135 | --------------------------------------------------------------------------------