Skip to content
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

[std.algorithm.searching] Add extrema to compute min and max #8727

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
131 changes: 129 additions & 2 deletions std/algorithm/searching.d
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ $(T2 commonPrefix,
`commonPrefix("parakeet", "parachute")` returns `"para"`.)
$(T2 endsWith,
`endsWith("rocks", "ks")` returns `true`.)
$(T2 extrema, `extrema([2, 1, 3, 5, 4])` returns `[1, 5]`.)
$(T2 find,
`find("hello world", "or")` returns `"orld"` using linear search.
(For binary search refer to $(REF SortedRange, std,range).))
Expand Down Expand Up @@ -3614,7 +3615,7 @@ Note:

See_Also:

$(LREF maxElement), $(REF min, std,algorithm,comparison), $(LREF minCount),
$(LREF extrema), $(LREF maxElement), $(REF min, std,algorithm,comparison), $(LREF minCount),
$(LREF minIndex), $(LREF minPos)
*/
auto minElement(alias map = (a => a), Range)(Range r)
Expand Down Expand Up @@ -3754,7 +3755,7 @@ Note:

See_Also:

$(LREF minElement), $(REF max, std,algorithm,comparison), $(LREF maxCount),
$(LREF extrema), $(LREF minElement), $(REF max, std,algorithm,comparison), $(LREF maxCount),
$(LREF maxIndex), $(LREF maxPos)
*/
auto maxElement(alias map = (a => a), Range)(Range r)
Expand Down Expand Up @@ -3860,6 +3861,132 @@ if (isInputRange!Range && !isInfinite!Range &&
assert(arr.maxElement!"a.val".val == 1);
}

/** Returns an array of the minimum and maximum element in `r`.
* Performs `< 3n/2` comparisons, unlike the naive `< 2n`.
* Params:
* r = The range to traverse.
*/
// TODO alias map = a => a
ElementType!Range[2] extrema(Range)(Range r)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The explicit ElementType!Range makes me wonder what happens when the elements are const, immutable, or shared.

if (isInputRange!Range && !isInfinite!Range)
in (!r.empty)
{
static if (isRandomAccessRange!Range && hasLength!Range)
{
if (r.length == 1)
return [r[0], r[0]];

typeof(return) result;
size_t i;
if (r.length & 1) // odd
{
result = [r[0], r[0]];
i = 1;
}
else
{
result = (r[0] < r[1]) ? [r[0], r[1]] : [r[1], r[0]];
i = 2;
}
// iterate pairs
const imax = r.length;
for (; i != imax; i += 2)
{
// save work
if (r[i] < r[i+1])
{
if (r[i] < result[0])
result[0] = r[i];
if (r[i+1] > result[1])
result[1] = r[i+1];
}
else
{
if (r[i+1] < result[0])
result[0] = r[i+1];
if (r[i] > result[1])
result[1] = r[i];
}
}
return result;
}
else
{
auto first = r.front;
r.popFront;
if (r.empty)
return [first, first];

typeof(return) result = (first < r.front) ? [first, r.front] : [r.front, first];
// iterate pairs
while (true)
{
r.popFront;
if (r.empty)
return result;
first = r.front;
r.popFront;
if (r.empty)
{
if (first < result[0])
result[0] = first;
else if (first > result[1])
result[1] = first;
return result;
}
// save work
if (first < r.front)
{
if (first < result[0])
result[0] = first;
if (r.front > result[1])
result[1] = r.front;
}
else
{
if (r.front < result[0])
result[0] = r.front;
if (first > result[1])
result[1] = first;
}
}
}
}

///
@safe unittest
{
assert(extrema([5,2,9,4,1]) == [1, 9]);
}

@safe unittest
{
assert(extrema([8,3,7,4,9]) == [3, 9]);
assert(extrema([1,5,3,2]) == [1, 5]);
assert(extrema([2,3,3,2]) == [2, 3]);

import std.range;
assert(iota(2, 5).extrema == [2, 4]);
assert(iota(3, 7).retro.extrema == [3, 6]);

import std.internal.test.dummyrange;
foreach (DummyType; AllDummyRanges)
{
DummyType d;
assert(d.extrema == [1, 10]);
}

version (StdRandomTests)
foreach (i; 0 .. 1000)
{
import std.random;
auto arr = generate!(() => uniform(0, 100)).takeExactly(uniform(1, 10)).array;
auto result = arr.extrema;
assert(result[0] == arr.minElement);
assert(result[1] == arr.maxElement);
}
}

// minPos
/**
Computes a subrange of `range` starting at the first occurrence of `range`'s
Expand Down