-
Notifications
You must be signed in to change notification settings - Fork 17.8k
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
sort: add Find #50340
Comments
By the way, even in the event type parameters cause a rethink of this package, a lighter-weight |
How would this work? I was under the impression that the reason for the secondary
Edit: Actually, I think that the heap method would be better as it doesn't require as many copies, and potentially allocations, when adding elements thanks to not having to insert into the middle of a slice. It does use interfaces if using |
Whatever else it might do, it can do another test. It only has <= but two calls of that will tell it == (NaNs aside, but we're already on thin ice with NaNs. They're not even numbers.) In case it's not clear from what I wrote above, that final test (or two) may be necessary, but I want the function to do it, not the caller. |
As proposed, there's no way for the function to determine actual equality. The function only takes the index to check against and, therefore, knows nothing about the value actually being checked. For example, given
It doesn't have The only mention that the proposal seems to make for separately determining equality is
but it's not clear what exactly that would look like. It sounds like you're saying that This present := sort.Contains(
len(data),
func(i int) bool { return data[i] >= x },
func(i int) bool { return data[i] == x },
)
if present {
// x is present in data
} doesn't seem much better to me than this i := sort.Search(len(data), func(i int) bool { return data[i] >= x })
if (i < len(data)) && (data[i] == x) {
// x is present at data[i]
} Edit: It occurs to me that this is fairly straightforwards with generics: func Contains[T any, S ~[]T](s S, x T, less func(v1, v2 T) bool) bool {
i := sort.Search(len(s), func(i int) bool { return less(x, s[i]) }) - 1
return (i >= 0) && !less(s[i], x)
} This is limited to slices, but that probably covers 99% of cases. A more complicated one that covers other cases is also possible, but probably not worth the trouble. Edit 2: Fix an instance of |
I had forgotten the challenge of moving the equality check away from the caller. I hadn't thought it through - even though I remember talking to @griesemer about the challenge when he was designing it. Mea ignoramica. But that affects only the mechanism, not my desire, which is to simplify the API. I like your solution, at least as a discussion point. I can't help feel there's a clean API trying to be found. C's bsearch isn't all that nice to use either, though. |
There's even this nice (untested) possibility for the simplest and possibly commonest case, if we can choose a different name for it.
|
I'm probably about to say something completely wrong, but isn't the problem solved by asking for a function able to report for multiple states? Like here we're focusing on a closure like |
This proposal has been added to the active column of the proposals project |
It sounds like we agree that sort.Search is hard to use, but there's not an obvious replacement. In particular, anything that can report an exact position has to take a different function (a 3-way result like strings.Compare, or multiple functions). For sake of argument, we could do:
where the (In comparison, sort.Search's f reports whether the item being sought is less than the element at position i; that is, it's Less(item, elem[i]), whereas Find takes Cmp(item, elem[i]).) Is that something we should consider adding? |
When we do resolve this discussion, perhaps we should revisit #47619 (comment). |
@rsc I am in favor of adding a function with the signature you suggest, and happy with the name. |
Sounds like people are generally in favor of adding sort.Find. Do I have that right? Does anyone object to adding Find (and slices.BinaryFind and slices.BinaryFindFunc)? |
I am at least partially responsible for the design of the original API and I also take Rob's view that it's needlessly awkward in the common case (it's also annoying because it's backward from most of the rest of the sort package's API - you have to think in terms of greater-than rather than less-than, but that's another matter).
I think I'd find it be more intuitive to say that What would the signature of
for example:
Maybe a good reason to add a
|
I think the analogous slices signatures would be func BinaryFind[E comparable](s []E, E) int
func BinaryFindFunc[E any](s []E, func(E) int) int It's a reasonable question to ask whether func BinaryFindFunc[E any](s []E, v E, func(E, E) int) int I'm not sure. I think the main argument for the latter would be that we can pass functions like |
The main reason from my point of view is that the item that's being searched for is rarely a constant, so the former always in practice requires the caller to create a closure that refers to that value, which is unwieldy and not really that much easier to use than Example calls:
The latter is the clear winner, I think - it's shorter and easier to get right. With the closure versions, I suspect it might end up more efficient too, because |
The three-argument FindFunc (FindFunc2 above) would be different from the other Funcs we have, like IndexFunc. It doesn't make sense to me to diverge from those. Should we leave BinaryFindFunc out until we are more sure it's needed? |
I feel like the |
I'd suggest that A quick scan of a random corpus of a few million lines of code turned up 20 uses of One of the nice things about
On the other hand, with a two-argument form, many existing functions and methods can be used as they are:
I'd argue that passing a compare function is more natural than passing a single argument function, has less potential for subtle bugs, and makes better use of existing available functions and methods. I don't believe this is about closures being unwieldy. Even if closures were more syntactically lightweight, you'd still need to remember to pass the arguments to the underlying comparison function (and there will usually be an underlying comparison function, I suspect) in the right order. |
One other thing that's worth clarifying: currently With |
I think |
Okay, so it sounds like we all agree on sort.Find. slices.Anything is a bit harder. It sounds like maybe the signature should be different from sort.Find. Perhaps slices.BinarySearchFunc should change to be the FindFunc2 we've been talking about.
and maybe we should change it to:
That would line up well with what other languages do and it avoids the problems @rogpeppe points out. Thoughts? |
CC @eliben |
I agree with decoupling the interfaces the The changes to I don't have a preference w.r.t. |
I believe as per this comment the new semantics will be returning the first (arbitrary) index |
Now that the new functions have landed in Unless @robpike you have a prototype already that you want to send... |
Change https://go.dev/cl/396514 mentions this issue: |
This came up on golang-nuts, where someone asked the predicate-based version to return. I also think it should. It is easy, when you have a comparison function, to get to a predicate: var b T
pred := func(a T) bool { return cmp(a, b) >= 0 } But not the other way around, so a predicate is more general. I used the predicate version of When I saw this issue fly by, I assumed this would be about adding a simpler version of |
I agree that a predicate is more general, but the slice still has to be sorted according to the predicate. And the comparison function seems easier to reason about. And given a predicate you can get the comparison by calling the predicate twice. And So, maybe? |
For #50340 Change-Id: I3b4d278affc8e7ec706db8c9777f7a8c8ce7441d Reviewed-on: https://go-review.googlesource.com/c/go/+/396514 Reviewed-by: Ian Lance Taylor <[email protected]> Trust: Cherry Mui <[email protected]> Run-TryBot: Russ Cox <[email protected]> Auto-Submit: Russ Cox <[email protected]> TryBot-Result: Gopher Robot <[email protected]>
As long as the comparison function isn't required to only return |
@DeedleFake Yes, true, although we do have a fairly strong convention for the way that comparison functions work already.
which would allow Maybe worth considering? |
I was actually going to mention that in my comment, but decided against it because it seemed too silly, and it also doesn't work with the index-based search, anyways. |
How so? |
Because the comparison function in |
If anything, we should make the return type of the comparison stronger, not weaker. That is, something like type Ordering int
const (
Lt Ordering = -1
Eq Ordering = 0
Gt Ordering = 1
)
|
Oh, I didn't notice which constraint that was. Yeah, don't use |
@eliben thank you for CL https://go-review.googlesource.com/c/go/+/396514 which I believe implemented this feature. @eliben @rsc @ianlancetaylor shall we close this issue then? Just waiting for your confirmation before I close it. Thank you everyone for the discourse and hard work. |
Shipped in Go 1.19. |
I have never used
sort.Search
to build a sorted list (slice). It's too expensive and there are better ways. However, I use it often to do what its name suggests: search.The API is designed for the growth case, which is fine and general, but the general (growth) case is rare, perhaps very rare, and handling the return value is clumsy†.
I suggest adding another function, say
sort.Contains
, that just says yes or no to the question of presence of the element. Here is sort.Search's use for the pure search case, right from the documentation:Besides the messy handling, that extra comparison has always bothered me.
Here is what that would look like with the proposed function:
This seems so much more intuitive and semantically lightweight. If you needed the index - but why would you if you weren't going to insert there? - you could go back to the existing
Search
function.It should be very simple to implement by having both
Search
andContains
call a helper that reports presence, and returning the appropriate result, or by just writingContains
itself - likeSearch
, it's very short.I already filled in the proposal questionnaire here: #45624
† And poorly documented, but that is fixable.
The text was updated successfully, but these errors were encountered: