5

I have a list of multidimensional points of type Points.

I've implemented the sort.Sort interface and can now sort by y value.

e.g.

type Points []*Point

func (points Points) Len() int {
    return len(points)
}
func (points Points) Less(i, j int) bool {
    return points[i].y < points[j].y
}
func (points Points) Swap(i, j int) {
    points[i], points[j] = points[j], points[i]
}

type Point struct {
    x int
    y int
    country_id int
}

Now I want to sort my points by x value instead of y value.

My idea is to use an if statement with a global flag (which can be switched on or off before sorting):

func (points Points) Less(i, j int) bool {
    if SORT_BY_X {
        return points[i].x < points[j].x
    }
    return points[i].y < points[j].y
}

Is there a better way of doing this? Should I be implementing Less multiple times? What if I was sorting tables of data by columns for example?

Rusty Rob
  • 16,489
  • 8
  • 100
  • 116

2 Answers2

7

Ah, this is interesting: sort.Sort() expects the type to define an ordering and some array operations. You can have "X-sortable point list" and "Y-sortable point list" types, but making them share the array ops works differently from in other languages because Go doesn't use inheritance.

The first way I thought of is to create XSortablePoints and YSortablePoints types that each independently implement sort.Interface, and convert your Points instance to whichever one you need at the moment--see here: http://play.golang.org/p/9V3WlKjOwX.

Then nemo had a better way: type embedding allows XSortablePoints and YSortablePoints to share the functions for array operations. Also, nemo doesn't save the sortable types in a variable, which makes sense as they only exist for this one sort call. Here's adjusted sample code: http://play.golang.org/p/wNm-ilM18n

Note that neither of these approaches actually copies your point data when you cast, just the slice header. You can see that by looking at the pointer addresses printed out by the first example.

You can get fancier: there's a Points.Sort taking an arbitrary comparison function at http://play.golang.org/p/4PmJVi2_7D. I think the brute-force approach of just defining more types is fine as long as you only have two or three orderings, but situations vary. Note that for types much bigger than the points here, you might want to define the comparer to take pointers rather than values, to avoid copying.

re: SORT_BY_X: I'd generally avoid global mode-setting variables that you update as the program runs, because there are lots of ways those can come back to bite you. For example, maybe you'll have two parallel goroutines someday and then trouble happens when they both access the global at once. Or maybe some code will work when the initial value of SORT_BY_X is false, then someday fail because it was left true after another task ran. If you do find yourself needing a mode variable, figure out if, instead of it being global, you could make it a function parameter or attach it to an object.

Finally, there might be a package that already provides some of the higher-level functionality you want. For example, there are some packages related to geographic data listed here: https://code.google.com/p/go-wiki/wiki/Projects#GIS

twotwotwo
  • 28,310
  • 8
  • 69
  • 56
  • 1
    Thanks I appreciate this & I agree it's not great using globals in such a way. At least now I only have to write a Less function for each dimension. (In python all this code is a one liner but it's not as fast). I suppose now we have the overhead of an extra function call for each comparison? I wonder if you could use reflect ( http://stackoverflow.com/questions/18930910/golang-access-struct-property-by-name ) in a closure in a function which takes 'x' or 'y' as a parameter and returns a Less function. Then we could have pts.sort(pts.get_less('x')) for example. – Rusty Rob Nov 04 '13 at 00:10
  • 2
    Only problem now is if I also have vectors and want to have vectors.sort() too ;) I'm enjoying go lang though - I have to write slightly more code than my usual python one liners but I appreciate the speed & simplicity of not having too many key words in the language. – Rusty Rob Nov 04 '13 at 00:12
  • my final question is how come you changed type Points []*Point to type Points []Point (without the *). I suppose then it swaps the points by value rather than the references. – Rusty Rob Nov 04 '13 at 00:13
  • Ah I just found this: https://github.com/pmylund/sortutil It uses reflect which isn't as efficient so I'll be using your solution for what I need. – Rusty Rob Nov 04 '13 at 00:27
  • 2
    If you need more than a couple sort orders for vectors, passing in a comparison function like in [the second example I went back and added](http://play.golang.org/p/Q1uOHOOT4R) might help. On `[]Point` vs. `[]*Point`, either can work and it can be a close tradeoff for small objects: `[]*Point` involves more pointer dereferencing, `[]Point` more copying. (Also, only `*Point` can be `nil`, but that's semantics not perf.) You could rig up a toy program to see how each works for your task; not at all sure what would come out of it. – twotwotwo Nov 04 '13 at 00:29
6

In addition to user2714852's answer, you can use the same technique as is already used for reversing sorting in the sort package: shadowing the Less() definition.

While this is similar to what was already proposed, the way of getting there is a bit different (example on play). You define your points:

type Points []Point

func (points Points) Swap(i, j int) {
    points[i], points[j] = points[j], points[i]
}

func (points Points) Len() int {
    return len(points)
}

And for each sorting method you implement your own type which embeds your Points type:

type XPoints struct {
    Points
}

func (points XPoints) Less(i,j int) bool {
    return points.Points[i].x < points.Points[j].x
}

type YPoints struct {
    Points
}

func (points YPoints) Less(i, j int) bool {
    return points.Points[i].y < points.Points[j].y
}

Now you can use the different sorting methods like this:

pts := Points{{1, 2, 3}, {2, 1, 3}}

sort.Sort(XPoints{pts})
fmt.Println("X-sorted points:", pts)

sort.Sort(YPoints{pts})
fmt.Println("Y-sorted points:", pts)
nemo
  • 55,207
  • 13
  • 135
  • 135
  • 1
    Good point and props for the stdlib link as well--upboated and added an example crediting you. Had played with embedding but stumbled by trying the incorrect `xpts[i].x` instead of `xpts.Points[i].x`. – twotwotwo Nov 04 '13 at 01:24
  • This is nice. I don't quite understand embedding. From reading the code, I understand XPoints{pts} implements Less, however I don't see how it also implements Swap & Len. I see that Points implements Swap & Len, I don't understand how Points transfers it's "implementation" to XPoints. – Rusty Rob Nov 04 '13 at 01:28
  • 2
    robert: [Effective Go](http://golang.org/doc/effective_go.html#embedding) and [the spec](http://golang.org/ref/spec#Struct_types) go into more detail. If, as in Nemo's code, your struct's definition includes another type like `Points` and doesn't assign it a name, your new struct picks up all the methods of `Points` that aren't hidden by methods of `XPoints`/`YPoints`. To access hidden methods of the embedded `Points` object or use operators on it, you access it via `obj.Points`--that's what Nemo's `points.Points[i]` is doing in his `Less` implementations. – twotwotwo Nov 04 '13 at 01:38
  • Thanks a lot. It's all starting to make sense now :) – Rusty Rob Nov 04 '13 at 02:04