-
Notifications
You must be signed in to change notification settings - Fork 209
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add generic less function for SortSlices #67
Comments
For Bool, Int, Int8, Int16, Int32, Int64, Uint, Uint8, Uint16, Uint32, Uint64, Uintptr, the comparison is obvious. My overall thoughts/questions:
\cc @bcmills who originally pushed for |
Panic on chan and func. I'd just convert UnsafePointer to a uintptr; it's runtime dependent but so are most uintptrs. Defining an ordering for maps is straightforward, but I don't see the use case. Maybe panic there as well. If you want an ordering, then: Take the union of all keys in both maps, sort it, compare the value for each key, first inequality defines the ordering. An absent key is less than a present one. |
If the only use case is for order-independent equality, then this feels like an implementation detail. Channels are comparable, so why can't I ask if my So I vote for |
I like For semantics, how about saying that it will sort any values that are comparable with the That would fit the use cases I've seen historically. It would still be possible to define custom comparison operations if one really wants to order non-comparable values. How does that sound? |
|
If that's the case, then I don't really see that spelling it I've made a PR (#71) so that we've got something concrete to talk about. |
I've commented on the PR with my concerns, which can be summarized as:
Stepping back for a moment, I think we're solving a harder problem than what is necessary. The original problem only cares about treating a slice of comparable elements as a set. Sorting them and dealing with order is a red herring. What if we provide the following instead: // SliceToSets returns an Option that converts all []T to a map[T]struct{},
// where T is any comparable type, which is specified by passing in a value of each type.
func SlicesToHashSets(typ ...interface{}) cmp.Options Obviously There is an open question whether the transformation should be a set (no duplicates) or a multi-set (duplicates allowed). Using a map as the output implies set, not multi-set. |
Other than that, I think that something like a reasonable idea. Unfortunately, I think that's a show stopper in quite a few cases, because we often do care about the total non-unique element count. In particular we almost always care that the result does not have more elements in than we expect, regardless of how many unique elements there are. But there's a possible workaround:
I'm not sure about the typ specification though, because it doesn't allow
Another is to lose the implicit filter and just expect the user to filter appropriately.
BTW there are a few places that I might have used GenericLess when using sort.Sort (or sort.Slice) in the past, rather than defining a custom struct comparison function. Mostly for semi-throwaway commands, but still perhaps a use case worth bearing in mind. The cmp package is not a heavy dependency. |
I feel somewhat strongly about making the helpers have some form of implicit type filter. My experience with Switching it to Perhaps we could go with an API like the following? // SortComparable returns an Option that sorts all []V, where V is any type
// assignable to a comparable type T, specified by passing in the slice of that type.
// SortComparable panics if the element type is not comparable.
//
// The exact ordering is unspecified except that it is equivalent to sorting
// according to Hash(v), where Hash returns a unique hash for every value.
//
// The dedup bool controls whether duplicate entries should be collapsed.
func SortComparable(dedup bool, typs ...interface{}) cmp.Option {
var ts []reflect.Type
for _, typ := range typs {
t := reflect.TypeOf(typ)
if t.Kind() != reflect.Slice {
panic(fmt.Sprintf("%v must be slice", t))
}
t = t.Elem()
if !t.Comparable() {
panic(fmt.Sprintf("%v must be comparable", t))
}
ts = append(ts, t)
}
return cmp.FilterPath(func(p cmp.Path) bool {
if t := p.Last().Type(); t != nil && t.Kind() == reflect.Slice {
for _, t2 := range ts {
if t.Elem().AssignableTo(t2) {
return true
}
}
}
return false
}, cmpopts.SortSlices(genericLess))
} The
If there are sufficient use-cases, it might be worth investigating in the future whether to make |
That code doesn't implement the dedup parameter, though it wouldn't be hard. I actually quite like your idea of using maps rather than defining a generic less function.
Then the de-duplication mechanism is clear, there's no arbitrary ordering, and we don't need to remember the sense of the dedup bool parameter. It also makes it possible to specify frequency counts directly as a |
What is the point of |
To my mind the name If we have something named Having said that, spelling I guess it depends on whether one should name something after what it does or how it is intended to be used. I can see arguments both ways. |
The consistent style so far is:
Since In regards to two separate functions ( |
Hi gophers, A lesser issue is that sorting slices takes O(n log n) time, whereas just comparing set equality should be linear using a map as a multi-set: The other thing I'd add is that objects within a slice need not be comparable (i.e. less function doesn't necessarily have to be defined), they only need to be distinguishable. If I'm understanding the previous messages correctly, it looks like For now, I've written a custom method in my code to do this, but it would be nice to have some standard method. FWIW python has |
Being "comparable" is necessary for the approach that places all these objects in a map as the key.
That would be interesting, but is beyond the scope of this issue. Unless go2 enables users to provide user-defined equality and hashability for maps, we shouldn't support this in |
Now that cmp has been available for a few years and there's been more experience with it. Is this option something still worth providing? |
FWIW I use a generic ordering function all the time - I ended up putting it into the quicktest package with this ugly and slow, but simple hack:
It would be nice to do a bit better, I think. Even the fmt package has a generic sorting algorithm for map keys now - maybe we could just copy that? |
Thanks for the reference. I'll scan all usages of |
Just voicing an opinion here, moving over from https://godoc.org/github.com/stretchr/testify/assert to |
I was thinking about this more and I wanted to note a complication with generic less functions. Suppose you had: type S struct {
F1, F2 int
}
var ss1, ss2 []S = ...
cmp.Equal(ss1, ss2,
cmpopts.SortSlices(GenericLess(S{})),
cmpopts.IgnoreFields(S{}, "F1"),
) There's a subtle problem here. The less operation used by To make this work, we would probably need
(!cmp.Less(x, y, opts...) && !cmp.Less(y, x, opts...)) == cmp.Equal(x, y, opts...) |
Another subtle complication: it's possible today that That is, if |
It's a bit of a pain writing a comparison function when we want slices to compare equal regardless of order. How about making SortSlices use a generic less function when it's argument is nil, so that in the common case we can just use SortSlices(nil) to check slice contents regardless of order and type?
The generic less function might be something like this, perhaps: https://play.golang.org/p/NQ1u36jKsi-
The text was updated successfully, but these errors were encountered: