]> Cypherpunks repositories - gostls13.git/commitdiff
sort: new example: programmatic sort by multiple keys
authorRob Pike <r@golang.org>
Tue, 2 Apr 2013 16:35:30 +0000 (09:35 -0700)
committerRob Pike <r@golang.org>
Tue, 2 Apr 2013 16:35:30 +0000 (09:35 -0700)
Demonstrates one way to sort a slice of structs according
to different sort criteria, done in sequence.

One possible answer to a question that comes up often.

R=golang-dev, gri, bradfitz, adg, adg, rogpeppe
CC=golang-dev
https://golang.org/cl/8182044

src/pkg/sort/example_multi_test.go [new file with mode: 0644]

diff --git a/src/pkg/sort/example_multi_test.go b/src/pkg/sort/example_multi_test.go
new file mode 100644 (file)
index 0000000..8a2f277
--- /dev/null
@@ -0,0 +1,132 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package sort_test
+
+import (
+       "fmt"
+       "sort"
+)
+
+// A Change is a record of source code changes, recording user, language, and delta size.
+type Change struct {
+       user     string
+       language string
+       lines    int
+}
+
+type lessFunc func(p1, p2 *Change) bool
+
+// multiSorter implements the Sort interface, sorting the changes within.
+type multiSorter struct {
+       changes []Change
+       less    []lessFunc
+}
+
+// Sort sorts the argument slice according to the less functions passed to OrderedBy.
+func (ms *multiSorter) Sort(changes []Change) {
+       sort.Sort(ms)
+}
+
+// OrderedBy returns a Sorter that sorts using the less functions, in order.
+// Call its Sort method to sort the data.
+func OrderedBy(less ...lessFunc) *multiSorter {
+       return &multiSorter{
+               changes: changes,
+               less:    less,
+       }
+}
+
+// Len is part of sort.Interface.
+func (ms *multiSorter) Len() int {
+       return len(ms.changes)
+}
+
+// Swap is part of sort.Interface.
+func (ms *multiSorter) Swap(i, j int) {
+       ms.changes[i], ms.changes[j] = ms.changes[j], ms.changes[i]
+}
+
+// Less is part of sort.Interface. It is implemented by looping along the
+// ordering functions until it finds a comparison that is either Less or
+// !Less. Note that it can call the ordering functions twice per call. We
+// could change the ordering functions to return -1, 0, 1 and reduce the
+// number of calls for greater efficiency: an exercise for the reader.
+func (ms *multiSorter) Less(i, j int) bool {
+       p, q := &ms.changes[i], &ms.changes[j]
+       // Try all but the last comparison.
+       var k int
+       for k = 0; k < len(ms.less)-1; k++ {
+               less := ms.less[k]
+               switch {
+               case less(p, q):
+                       // p < q, so we have a decision.
+                       return true
+               case less(q, p):
+                       // p > q, so we have a decision.
+                       return false
+               }
+               // p == q; try the next comparison.
+       }
+       // All comparisons to here said "equal", so just return whatever
+       // the final comparison reports.
+       return ms.less[k](p, q)
+}
+
+var changes = []Change{
+       {"gri", "Go", 100},
+       {"ken", "C", 150},
+       {"glenda", "Go", 200},
+       {"rsc", "Go", 200},
+       {"r", "Go", 100},
+       {"ken", "Go", 200},
+       {"dmr", "C", 100},
+       {"r", "C", 150},
+       {"gri", "Smalltalk", 80},
+}
+
+// ExampleMultiKeys demonstrates a technique for sorting a struct type using different
+// sets of multiple fields in the comparison. We chain together "Less" functions, each of
+// which compares a single field.
+func Example_sortMultiKeys() {
+       // Closures that order the Change structure.
+       user := func(c1, c2 *Change) bool {
+               return c1.user < c2.user
+       }
+       language := func(c1, c2 *Change) bool {
+               return c1.language < c2.language
+       }
+       increasingLines := func(c1, c2 *Change) bool {
+               return c1.lines < c2.lines
+       }
+       decreasingLines := func(c1, c2 *Change) bool {
+               return c1.lines > c2.lines // Note: > orders downwards.
+       }
+
+       // Simple use: Sort by user.
+       OrderedBy(user).Sort(changes)
+       fmt.Println("By user:", changes)
+
+       // multiSorter implements the Sort interface, so we can also do this.
+       sort.Sort(OrderedBy(user, increasingLines))
+       fmt.Println("By user,<lines:", changes)
+
+       // More examples.
+       OrderedBy(user, decreasingLines).Sort(changes)
+       fmt.Println("By user,>lines:", changes)
+
+       OrderedBy(language, increasingLines).Sort(changes)
+       fmt.Println("By language,<lines:", changes)
+
+       OrderedBy(language, increasingLines, user).Sort(changes)
+       fmt.Println("By language,<lines,user:", changes)
+
+       // Output:
+       //By user: [{dmr C 100} {glenda Go 200} {gri Smalltalk 80} {gri Go 100} {ken Go 200} {ken C 150} {r Go 100} {r C 150} {rsc Go 200}]
+       //By user,<lines: [{dmr C 100} {glenda Go 200} {gri Smalltalk 80} {gri Go 100} {ken C 150} {ken Go 200} {r Go 100} {r C 150} {rsc Go 200}]
+       //By user,>lines: [{dmr C 100} {glenda Go 200} {gri Go 100} {gri Smalltalk 80} {ken Go 200} {ken C 150} {r C 150} {r Go 100} {rsc Go 200}]
+       //By language,<lines: [{dmr C 100} {ken C 150} {r C 150} {gri Go 100} {r Go 100} {ken Go 200} {glenda Go 200} {rsc Go 200} {gri Smalltalk 80}]
+       //By language,<lines,user: [{dmr C 100} {ken C 150} {r C 150} {gri Go 100} {r Go 100} {glenda Go 200} {ken Go 200} {rsc Go 200} {gri Smalltalk 80}]
+
+}