-
-
Notifications
You must be signed in to change notification settings - Fork 726
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
Fix #10257 - variadic overload of std.algorithm.searching.countUntil should return which needle was found #5618
Conversation
Thanks for your pull request, @wilzbach! Bugzilla referencesYour PR doesn't reference any Bugzilla issue. If your PR contains non-trivial changes, please reference a Bugzilla issue or create a manual changelog.
|
std/algorithm/searching.d
Outdated
|
||
See_Also: $(REF indexOf, std,string) | ||
+/ | ||
ptrdiff_t countUntil(alias pred = "a == b", R, Rs...)(R haystack, Rs needles) | ||
auto countUntil(alias pred = "a == b", R, Rs...)(R haystack, Rs needles) | ||
if (isForwardRange!R | ||
&& Rs.length > 0 | ||
&& isForwardRange!(Rs[0]) == isInputRange!(Rs[0]) | ||
&& is(typeof(startsWith!pred(haystack, needles[0]))) | ||
&& (Rs.length == 1 | ||
|| is(typeof(countUntil!pred(haystack, needles[1 .. $]))))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CC @UplinkCoder in case you haven't seen how CTFE is abused at Phobos ;-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@wilzbach I am painfully aware.
Awesome. Looks good to me. |
std/algorithm/searching.d
Outdated
static if (hasLength!R) //Note: Narrow strings don't have length. | ||
{ | ||
//We delegate to find because find is very efficient. | ||
//We store the length of the haystack so we don't have to save it. | ||
auto len = haystack.length; | ||
auto r2 = find!pred(haystack, needles[0]); | ||
if (!r2.empty) | ||
return cast(typeof(return)) (len - r2.length); | ||
return cast(T) (len - r2.length); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return ptrdiff_t(len - r2.length)
and remove the alias T
above.
Also use ptrdiff_t(0)
and so on on the the few places below, to prevent incorrect return type deduction, due to the use static if
s.
std/algorithm/searching.d
Outdated
@@ -799,15 +802,33 @@ if (isForwardRange!R | |||
haystack.popFront(); | |||
} | |||
} | |||
//Because of @@@8804@@@: Avoids both "unreachable code" or "no return statement" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't this simply because there's no return statement in the else
branch above?
std/algorithm/searching.d
Outdated
@@ -799,15 +802,33 @@ if (isForwardRange!R | |||
haystack.popFront(); | |||
} | |||
} | |||
//Because of @@@8804@@@: Avoids both "unreachable code" or "no return statement" | |||
static if (isInfinite!R) assert(0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
!isInfinite!R
should be part of the template constraints, IMO.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why? There are legitimate use cases for using countUntil
with an infinite range (I added also a test to ensure that this works)
{ | ||
ptrdiff_t steps, needle; // both -1 when nothing was found | ||
alias steps this; // compatible with previous ptrdiff_t return type | ||
ptrdiff_t opIndex(size_t idx) // faking a tuple |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm playing with the idea to change the signature to ptrdiff_t opIndex(bool idx)
. Due to implicit conversion, res[0]
and res[1]
would still work. Looks like a hack, but since this type models Tuple!(ptrdiff_t, "steps", ptrdiff_t needle)
and tuples do not satisfy isRandomAccessRange
, we don't need to use size_t
either. Using std.typecons.Flag
could also work, but I'm failing to find a good name for the template parameter.
Alternatively, if we had DIP66 implemented, we could do:
struct Result
{
import std.meta : AliasSeq;
alias Members = AliasSeq!(ptrdiff_t, ptrdiff_t);
Members _members;
alias steps = _members[0];
alias needle = _members[1];
alias _members this; // 1)
alias steps this; // 2)
}
unittest
{
auto r = Result(1, 2);
assert(r[0] == 1); // 1)
assert(r[1] == 2); // 1)
assert(r == 1); // 2)
}
But unfortunately you can only choose between 1) or 2). (We also can't use T opCast(T : ptrdiff_t)() { return steps; }
, because this requires the use of explicit casting - such as assert(cast(ptrdiff_t)r == 1)
- and so it would be a breaking change.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm playing with the idea to change the signature to ptrdiff_t opIndex(bool idx). Due to implicit conversion, res[0] and res[1] would still work.
Yeah that's quite weird. Imho - if we want to improve things - we should look into how we could trigger deprecation warnings if the alias steps this
is used, but I'm not sure whether that's possible.
tuples do not satisfy isRandomAccessRange
This emulation isn't even an InputRange
.
std/algorithm/searching.d
Outdated
result.needle = -1; | ||
result.steps = -1; | ||
//Because of @@@8804@@@: Avoids both "unreachable code" or "no return statement" | ||
static if (isInfinite!R) assert(0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The assert(0)
shouldn't be necessary if you return unconditionally here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks a bit on the clever side. All we seem to need here is:
struct Result
{
ptrdiff_t count;
alias count this;
size_t match;
}
Then return that guy. We can even generalize that (if it has enough pick-up) to a ExtraData!(ptrdiff_t, size_t)
in which the first member is the supertype and the second is "extra data".
Regardless, this is liable to break code - what with people using auto
and all. How about just adding a new function countUntilWhich
that takes a ref size_t
as its second argument?
std/algorithm/searching.d
Outdated
@@ -749,36 +749,38 @@ if (isInputRange!R && !isInfinite!R) | |||
$(D startsWith!pred(haystack, needles)) is $(D true). If | |||
$(D startsWith!pred(haystack, needles)) is not $(D true) for any element in | |||
$(D haystack), then $(D -1) is returned. | |||
If more than one needle is provided, `countUntil` will wrap the result | |||
in a tuple similar to `Tuple!(ptrdiff_t, "steps", ptrdiff_t needle)` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you mean to quote "needle"? Also, why is the tuple "similar to" and not exactly?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, got it it's the pseudo-tuple trick below.
std/algorithm/searching.d
Outdated
static if (needles.length == 1) | ||
{ | ||
ptrdiff_t result; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
move this to where it's used (quite a bit down it seems)
Yes in theory that's possible, but isn't this unlikely as the resulting type will behave as if it would be a
I would like to avoid introducing a new symbol for this seldom use-case. Hence, if we can't do the trick above, I think a template flag would be better e.g. |
3bdc293
to
d257ce3
Compare
TODO: Squash and update the commit message to reference GH issue #10257. |
…ntil should return which needle was found
Thanks a lot to @MartinNowak for suggesting this solution on Slack.
CC @RazvanN7