mirror of
https://github.com/dlang/phobos.git
synced 2025-04-29 22:50:38 +03:00
std.algorithm.searching: no mapping-specialization for extremum (#5001)
std.algorithm.searching: no mapping-specialization for extremum merged-on-behalf-of: Sebastian Wilzbach <sebi.wilzbach@gmail.com>
This commit is contained in:
parent
5356dc2353
commit
169c82c938
2 changed files with 188 additions and 21 deletions
22
changelog/std-algorithm-searching-extremum.dd
Normal file
22
changelog/std-algorithm-searching-extremum.dd
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
Performance improvements for `std.algorithm.searching.{min,max}Element`
|
||||||
|
|
||||||
|
$(REF minElement, std, algorithm, searching) and $(REF maxElement, std, algorithm, searching)
|
||||||
|
are now considerably faster (almost 2x in microbenchmarks)
|
||||||
|
as a special path for the identity case is provided. This allows the compiler
|
||||||
|
to make use of SSE instructions.
|
||||||
|
|
||||||
|
The exact improvements are:
|
||||||
|
|
||||||
|
$(CONSOLE
|
||||||
|
% dmd -release -O test.d && ./test
|
||||||
|
extremum.before = 54 secs, 12 ms, 347 μs, and 9 hnsecs
|
||||||
|
extremum.after = 29 secs, 521 ms, 896 μs, and 5 hnsecs
|
||||||
|
)
|
||||||
|
|
||||||
|
$(CONSOLE
|
||||||
|
% ldc -release -O3 test.d && ./test
|
||||||
|
extremum.before = 13 secs, 186 ms, 176 μs, and 4 hnsecs
|
||||||
|
extremum.after = 2 secs, 241 ms, 454 μs, and 9 hnsecs
|
||||||
|
)
|
||||||
|
|
||||||
|
$(LINK2 https://gist.github.com/wilzbach/9f757fa76200956aadb97059d614df34, See the benchmark code).
|
|
@ -1242,38 +1242,40 @@ Params:
|
||||||
Returns:
|
Returns:
|
||||||
The extreme value according to `map` and `selector` of the passed-in values.
|
The extreme value according to `map` and `selector` of the passed-in values.
|
||||||
*/
|
*/
|
||||||
private auto extremum(alias map = "a", alias selector = "a < b", Range)(Range r)
|
private auto extremum(alias map, alias selector = "a < b", Range)(Range r)
|
||||||
if (isInputRange!Range && !isInfinite!Range)
|
if (isInputRange!Range && !isInfinite!Range &&
|
||||||
|
is(typeof(unaryFun!map(ElementType!(Range).init))))
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
assert(!r.empty, "r is an empty range");
|
assert(!r.empty, "r is an empty range");
|
||||||
}
|
}
|
||||||
body
|
body
|
||||||
{
|
{
|
||||||
alias mapFun = unaryFun!map;
|
|
||||||
alias Element = ElementType!Range;
|
alias Element = ElementType!Range;
|
||||||
Unqual!Element seed = r.front;
|
Unqual!Element seed = r.front;
|
||||||
r.popFront();
|
r.popFront();
|
||||||
return extremum!(map, selector)(r, seed);
|
return extremum!(map, selector)(r, seed);
|
||||||
}
|
}
|
||||||
|
|
||||||
private auto extremum(alias map = "a", alias selector = "a < b", Range,
|
private auto extremum(alias map, alias selector = "a < b", Range,
|
||||||
RangeElementType = ElementType!Range)
|
RangeElementType = ElementType!Range)
|
||||||
(Range r, RangeElementType seedElement)
|
(Range r, RangeElementType seedElement)
|
||||||
if (isInputRange!Range && !isInfinite!Range &&
|
if (isInputRange!Range && !isInfinite!Range &&
|
||||||
!is(CommonType!(ElementType!Range, RangeElementType) == void))
|
!is(CommonType!(ElementType!Range, RangeElementType) == void) &&
|
||||||
|
is(typeof(unaryFun!map(ElementType!(Range).init))))
|
||||||
{
|
{
|
||||||
alias mapFun = unaryFun!map;
|
alias mapFun = unaryFun!map;
|
||||||
alias selectorFun = binaryFun!selector;
|
alias selectorFun = binaryFun!selector;
|
||||||
|
|
||||||
alias Element = ElementType!Range;
|
alias Element = ElementType!Range;
|
||||||
alias CommonElement = CommonType!(Element, RangeElementType);
|
alias CommonElement = CommonType!(Element, RangeElementType);
|
||||||
alias MapType = Unqual!(typeof(mapFun(CommonElement.init)));
|
|
||||||
|
|
||||||
Unqual!CommonElement extremeElement = seedElement;
|
Unqual!CommonElement extremeElement = seedElement;
|
||||||
|
|
||||||
|
alias MapType = Unqual!(typeof(mapFun(CommonElement.init)));
|
||||||
MapType extremeElementMapped = mapFun(extremeElement);
|
MapType extremeElementMapped = mapFun(extremeElement);
|
||||||
|
|
||||||
static if (isRandomAccessRange!Range && hasLength!Range)
|
// direct access via a random access range is faster
|
||||||
|
static if (isRandomAccessRange!Range)
|
||||||
{
|
{
|
||||||
foreach (const i; 0 .. r.length)
|
foreach (const i; 0 .. r.length)
|
||||||
{
|
{
|
||||||
|
@ -1301,7 +1303,55 @@ if (isInputRange!Range && !isInfinite!Range &&
|
||||||
return extremeElement;
|
return extremeElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
@safe pure nothrow unittest
|
private auto extremum(alias selector = "a < b", Range)(Range r)
|
||||||
|
if (isInputRange!Range && !isInfinite!Range &&
|
||||||
|
!is(typeof(unaryFun!selector(ElementType!(Range).init))))
|
||||||
|
{
|
||||||
|
alias Element = ElementType!Range;
|
||||||
|
Unqual!Element seed = r.front;
|
||||||
|
r.popFront();
|
||||||
|
return extremum!selector(r, seed);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we only have one statement in the loop it can be optimized a lot better
|
||||||
|
private auto extremum(alias selector = "a < b", Range,
|
||||||
|
RangeElementType = ElementType!Range)
|
||||||
|
(Range r, RangeElementType seedElement)
|
||||||
|
if (isInputRange!Range && !isInfinite!Range &&
|
||||||
|
!is(CommonType!(ElementType!Range, RangeElementType) == void) &&
|
||||||
|
!is(typeof(unaryFun!selector(ElementType!(Range).init))))
|
||||||
|
{
|
||||||
|
alias Element = ElementType!Range;
|
||||||
|
alias CommonElement = CommonType!(Element, RangeElementType);
|
||||||
|
Unqual!CommonElement extremeElement = seedElement;
|
||||||
|
alias selectorFun = binaryFun!selector;
|
||||||
|
|
||||||
|
// direct access via a random access range is faster
|
||||||
|
static if (isRandomAccessRange!Range)
|
||||||
|
{
|
||||||
|
foreach (const i; 0 .. r.length)
|
||||||
|
{
|
||||||
|
if (selectorFun(r[i], extremeElement))
|
||||||
|
{
|
||||||
|
extremeElement = r[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
while (!r.empty)
|
||||||
|
{
|
||||||
|
if (selectorFun(r.front, extremeElement))
|
||||||
|
{
|
||||||
|
extremeElement = r.front;
|
||||||
|
}
|
||||||
|
r.popFront();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return extremeElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
@safe pure unittest
|
||||||
{
|
{
|
||||||
// allows a custom map to select the extremum
|
// allows a custom map to select the extremum
|
||||||
assert([[0, 4], [1, 2]].extremum!"a[0]" == [0, 4]);
|
assert([[0, 4], [1, 2]].extremum!"a[0]" == [0, 4]);
|
||||||
|
@ -1310,14 +1360,25 @@ if (isInputRange!Range && !isInfinite!Range &&
|
||||||
// allows a custom selector for comparison
|
// allows a custom selector for comparison
|
||||||
assert([[0, 4], [1, 2]].extremum!("a[0]", "a > b") == [1, 2]);
|
assert([[0, 4], [1, 2]].extremum!("a[0]", "a > b") == [1, 2]);
|
||||||
assert([[0, 4], [1, 2]].extremum!("a[1]", "a > b") == [0, 4]);
|
assert([[0, 4], [1, 2]].extremum!("a[1]", "a > b") == [0, 4]);
|
||||||
|
|
||||||
|
// use a custom comparator
|
||||||
|
import std.math : cmp;
|
||||||
|
assert([-2., 0, 5].extremum!cmp == 5.0);
|
||||||
|
assert([-2., 0, 2].extremum!`cmp(a, b) < 0` == -2.0);
|
||||||
|
|
||||||
|
// combine with map
|
||||||
|
import std.range : enumerate;
|
||||||
|
assert([-3., 0, 5].enumerate.extremum!(`a.value`, cmp) == tuple(2, 5.0));
|
||||||
|
assert([-2., 0, 2].enumerate.extremum!(`a.value`, `cmp(a, b) < 0`) == tuple(0, -2.0));
|
||||||
|
|
||||||
|
// seed with a custom value
|
||||||
|
int[] arr;
|
||||||
|
assert(arr.extremum(1) == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@safe pure nothrow unittest
|
@safe pure nothrow unittest
|
||||||
{
|
{
|
||||||
// allow seeds
|
// 2d seeds
|
||||||
int[] arr;
|
|
||||||
assert(arr.extremum(1) == 1);
|
|
||||||
|
|
||||||
int[][] arr2d;
|
int[][] arr2d;
|
||||||
assert(arr2d.extremum([1]) == [1]);
|
assert(arr2d.extremum([1]) == [1]);
|
||||||
|
|
||||||
|
@ -1325,6 +1386,46 @@ if (isInputRange!Range && !isInfinite!Range &&
|
||||||
assert(extremum([2, 3, 4], 1.5) == 1.5);
|
assert(extremum([2, 3, 4], 1.5) == 1.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@safe pure unittest
|
||||||
|
{
|
||||||
|
import std.range : enumerate, iota;
|
||||||
|
|
||||||
|
// forward ranges
|
||||||
|
assert(iota(1, 5).extremum() == 1);
|
||||||
|
assert(iota(2, 5).enumerate.extremum!"a.value" == tuple(0, 2));
|
||||||
|
|
||||||
|
// should work with const
|
||||||
|
const(int)[] immArr = [2, 1, 3];
|
||||||
|
assert(immArr.extremum == 1);
|
||||||
|
|
||||||
|
// should work with immutable
|
||||||
|
immutable(int)[] immArr2 = [2, 1, 3];
|
||||||
|
assert(immArr2.extremum == 1);
|
||||||
|
|
||||||
|
// with strings
|
||||||
|
assert(["b", "a", "c"].extremum == "a");
|
||||||
|
|
||||||
|
// with all dummy ranges
|
||||||
|
import std.internal.test.dummyrange;
|
||||||
|
foreach (DummyType; AllDummyRanges)
|
||||||
|
{
|
||||||
|
DummyType d;
|
||||||
|
assert(d.extremum == 1);
|
||||||
|
assert(d.extremum!(a => a) == 1);
|
||||||
|
assert(d.extremum!`a > b` == 10);
|
||||||
|
assert(d.extremum!(a => a, `a > b`) == 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@nogc @safe nothrow pure unittest
|
||||||
|
{
|
||||||
|
static immutable arr = [7, 3, 4, 2, 1, 8];
|
||||||
|
assert(arr.extremum == 1);
|
||||||
|
|
||||||
|
static immutable arr2d = [[1, 9], [3, 1], [4, 2]];
|
||||||
|
assert(arr2d.extremum!"a[1]" == arr2d[1]);
|
||||||
|
}
|
||||||
|
|
||||||
// find
|
// find
|
||||||
/**
|
/**
|
||||||
Finds an individual element in an input range. Elements of $(D
|
Finds an individual element in an input range. Elements of $(D
|
||||||
|
@ -3237,14 +3338,21 @@ Returns: The minimal element of the passed-in range.
|
||||||
See_Also:
|
See_Also:
|
||||||
$(REF min, std,algorithm,comparison)
|
$(REF min, std,algorithm,comparison)
|
||||||
*/
|
*/
|
||||||
auto minElement(alias map = "a", Range)(Range r)
|
auto minElement(alias map, Range)(Range r)
|
||||||
if (isInputRange!Range && !isInfinite!Range)
|
if (isInputRange!Range && !isInfinite!Range)
|
||||||
{
|
{
|
||||||
return extremum!map(r);
|
return extremum!map(r);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ditto
|
/// ditto
|
||||||
auto minElement(alias map = "a", Range, RangeElementType = ElementType!Range)
|
auto minElement(Range)(Range r)
|
||||||
|
if (isInputRange!Range && !isInfinite!Range)
|
||||||
|
{
|
||||||
|
return extremum(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ditto
|
||||||
|
auto minElement(alias map, Range, RangeElementType = ElementType!Range)
|
||||||
(Range r, RangeElementType seed)
|
(Range r, RangeElementType seed)
|
||||||
if (isInputRange!Range && !isInfinite!Range &&
|
if (isInputRange!Range && !isInfinite!Range &&
|
||||||
!is(CommonType!(ElementType!Range, RangeElementType) == void))
|
!is(CommonType!(ElementType!Range, RangeElementType) == void))
|
||||||
|
@ -3252,6 +3360,15 @@ if (isInputRange!Range && !isInfinite!Range &&
|
||||||
return extremum!map(r, seed);
|
return extremum!map(r, seed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// ditto
|
||||||
|
auto minElement(Range, RangeElementType = ElementType!Range)
|
||||||
|
(Range r, RangeElementType seed)
|
||||||
|
if (isInputRange!Range && !isInfinite!Range &&
|
||||||
|
!is(CommonType!(ElementType!Range, RangeElementType) == void))
|
||||||
|
{
|
||||||
|
return extremum(r, seed);
|
||||||
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
@safe pure unittest
|
@safe pure unittest
|
||||||
{
|
{
|
||||||
|
@ -3299,7 +3416,13 @@ if (isInputRange!Range && !isInfinite!Range &&
|
||||||
{
|
{
|
||||||
DummyType d;
|
DummyType d;
|
||||||
assert(d.minElement == 1);
|
assert(d.minElement == 1);
|
||||||
|
assert(d.minElement!(a => a) == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// with empty, but seeded ranges
|
||||||
|
int[] arr;
|
||||||
|
assert(arr.minElement(42) == 42);
|
||||||
|
assert(arr.minElement!(a => a)(42) == 42);
|
||||||
}
|
}
|
||||||
|
|
||||||
@nogc @safe nothrow pure unittest
|
@nogc @safe nothrow pure unittest
|
||||||
|
@ -3329,21 +3452,37 @@ Returns: The maximal element of the passed-in range.
|
||||||
See_Also:
|
See_Also:
|
||||||
$(REF max, std,algorithm,comparison)
|
$(REF max, std,algorithm,comparison)
|
||||||
*/
|
*/
|
||||||
auto maxElement(alias map = "a", Range)(Range r)
|
auto maxElement(alias map, Range)(Range r)
|
||||||
if (isInputRange!Range && !isInfinite!Range &&
|
if (isInputRange!Range && !isInfinite!Range)
|
||||||
!is(CommonType!(ElementType!Range, RangeElementType) == void))
|
|
||||||
{
|
{
|
||||||
return extremum!(map, "a > b")(r);
|
return extremum!(map, "a > b")(r);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ditto
|
/// ditto
|
||||||
auto maxElement(alias map = "a", Range, RangeElementType = ElementType!Range)
|
auto maxElement(Range)(Range r)
|
||||||
(Range r, RangeElementType seed)
|
|
||||||
if (isInputRange!Range && !isInfinite!Range)
|
if (isInputRange!Range && !isInfinite!Range)
|
||||||
|
{
|
||||||
|
return extremum!`a > b`(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ditto
|
||||||
|
auto maxElement(alias map, Range, RangeElementType = ElementType!Range)
|
||||||
|
(Range r, RangeElementType seed)
|
||||||
|
if (isInputRange!Range && !isInfinite!Range &&
|
||||||
|
!is(CommonType!(ElementType!Range, RangeElementType) == void))
|
||||||
{
|
{
|
||||||
return extremum!(map, "a > b")(r, seed);
|
return extremum!(map, "a > b")(r, seed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// ditto
|
||||||
|
auto maxElement(Range, RangeElementType = ElementType!Range)
|
||||||
|
(Range r, RangeElementType seed)
|
||||||
|
if (isInputRange!Range && !isInfinite!Range &&
|
||||||
|
!is(CommonType!(ElementType!Range, RangeElementType) == void))
|
||||||
|
{
|
||||||
|
return extremum!`a > b`(r, seed);
|
||||||
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
@safe pure unittest
|
@safe pure unittest
|
||||||
{
|
{
|
||||||
|
@ -3392,7 +3531,14 @@ if (isInputRange!Range && !isInfinite!Range)
|
||||||
{
|
{
|
||||||
DummyType d;
|
DummyType d;
|
||||||
assert(d.maxElement == 10);
|
assert(d.maxElement == 10);
|
||||||
|
assert(d.maxElement!(a => a) == 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// with empty, but seeded ranges
|
||||||
|
int[] arr;
|
||||||
|
assert(arr.maxElement(42) == 42);
|
||||||
|
assert(arr.maxElement!(a => a)(42) == 42);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@nogc @safe nothrow pure unittest
|
@nogc @safe nothrow pure unittest
|
||||||
|
@ -3404,7 +3550,6 @@ if (isInputRange!Range && !isInfinite!Range)
|
||||||
assert(arr2d.maxElement!"a[1]" == arr2d[1]);
|
assert(arr2d.maxElement!"a[1]" == arr2d[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// minPos
|
// minPos
|
||||||
/**
|
/**
|
||||||
Computes a subrange of `range` starting at the first occurrence of `range`'s
|
Computes a subrange of `range` starting at the first occurrence of `range`'s
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue