Proposal Details

This proposal is for expanding the sort package in the stdlib to use generics. The Sort and Slices packages have been updated to use generics to cover all other functions in the sort package, but calling Sort() on an arbitrary data structure still requires that the provided Interface implements Less(i, j int) bool and Swap(i, j int) instead of allowing for generics. This would be helpful for cases where the primary keys/values of an ordered data structure are not integers.

Example:

package main

import (
    "fmt"
    "slices"
    "sort"
)

//**want to use this interface**
type CustomDataInterface interface {
    Len() int
    Less(a, b string) bool
    Swap(a, b string)
}

type CustomData struct {
    Dat []string
}

func (cd *CustomData) Len() int {
    return len(cd.Dat)
}

func (cd *CustomData) Less(a, b string) bool {
    return a < b
}

func (cd *CustomData) Swap(a, b string) {
    idxA := slices.IndexFunc(cd.Dat, func(x string) bool { return x == a })
    idxB := slices.IndexFunc(cd.Dat, func(x string) bool { return x == b })
    cd.Dat[idxA] = b
    cd.Dat[idxB] = a
    return
}

func main() {
    letters := []string{"b", "a", "c", "d"}
    CD := &CustomData{Dat: letters}

    fmt.Printf("%v", CD.Dat)
    fmt.Println()

    //slices.Sort(CD.Dat) //works for slices
    sort.Sort(CD)
    fmt.Printf("%v", CD.Dat)
}

./prog.go:46:12: cannot use CD (variable of type *CustomData) as sort.Interface value in argument to sort.Sort: *CustomData does not implement sort.Interface (wrong type for method Less) have Less(string, string) bool want Less(int, int) bool

It looks like this wouldn't be too difficult to implement since the sort package already has some generated code for generics. I also didn't find other issues mentioning this proposal but may be missing something obvious.

Comment From: seankhliao

It's unclear how the sort package would produce these keys of generic values to pass to the Less and Swap functions given it only knows Len (and therefore and index of 0 to (len-1)) of the structure to sort.

Comment From: randall77

This is possible but it would require using new names in the sort package. We cannot change sort.Sort itself (and sort.Interface) at this point.

The Swap part of this proposal doesn't make any sense to me. First, your implementation is O(n), which is bad, and second I don't think it handles duplicate strings correctly. I think Swap would have to be Swap(a,b *string), no? And at that point, why have Swap at all? If these new functions are generic on the element type, they can do the swap themselves without the interface. (Like slices.Sort does.) At that point, why not just use slices.SortFunc?

Comment From: gabyhelp

Similar Issues

  • https://github.com/golang/go/issues/47619
  • https://github.com/golang/go/issues/16721
  • https://github.com/golang/go/issues/45422
  • https://github.com/golang/go/issues/33818

(Emoji vote if this was helpful or unhelpful; more detailed feedback welcome in this discussion.)

Comment From: dshebib

Right, this might have to be implemented as part of an iter package that uses the seq interface to access members. Because it would only be able to access members linearly it will be less efficient than slices.sort() but would accept more complex sturctures than just slices. As far as whether it is worth it to add this function (perhaps in the iter package itself or some other experimental package), it seems like that's part of a greater discussion in #61897 and elsewhere. The lack of other proposals suggests that most people are fine sticking to slices for most use cases.

func (it iter.Seq[*V comparable]) IterSort()
func (it iter.Seq2[*K comparable, *V any]) IterSort()

Comment From: randall77

func (it iter.Seq[*V comparable]) IterSort()

I don't see how that signature works. Where is the result of the sort?

Given that under the hood it would have to use a slice anyway, I don't see the problem with:

s := slices.Collect(it)
slices.Sort(s)

And maybe it2 := slices.All(s) if you want to get back to an iterator afterwards.

Comment From: dshebib

The sort would occur in-place using the pointer returned by the iterator. This would cause the actual data to be sorted, not just as a copy of a slice.

Comment From: randall77

The sort would occur in-place using the pointer returned by the iterator. This would cause the actual data to be sorted, not just as a copy of a slice.

That's how C++ iterators work. That is not how Go iterators work.

Comment From: dshebib

You caught me 😆 I guess my question is why not include that functionality if iterators which return by address are already being considered?

Comment From: randall77

I don't think there are currently any proposals for return-by-address iterators. Of course, you could make an iterator that returns pointers to entries in some data structure. But they wouldn't be seekable iterators like C++ has, so I'm not sure how useful that would be. For sort, something tells me it would be completely useless.