diff --git a/std/algorithm/comparison.d b/std/algorithm/comparison.d index 4366aaacdaf..424e0c8fd2a 100644 --- a/std/algorithm/comparison.d +++ b/std/algorithm/comparison.d @@ -1270,6 +1270,9 @@ Params: Returns: The maximum of the passed-in args. The type of the returned value is the type among the passed arguments that is able to store the largest value. + +See_Also: + $(XREF_PACK algorithm,searching,maxElement) */ MaxType!T max(T...)(T args) if (T.length >= 2) @@ -1382,6 +1385,8 @@ Iterates the passed arguments and returns the minimum value. Params: args = The values to select the minimum from. At least two arguments must be passed, and they must be comparable with `<`. Returns: The minimum of the passed-in values. +See_Also: + $(XREF_PACK algorithm,searching,minElement) */ MinType!T min(T...)(T args) if (T.length >= 2) diff --git a/std/algorithm/package.d b/std/algorithm/package.d index 7ea08f12ccb..4f614bbe00f 100644 --- a/std/algorithm/package.d +++ b/std/algorithm/package.d @@ -35,6 +35,8 @@ $(TR $(TDNW Searching) $(SUBREF searching, findSplitBefore) $(SUBREF searching, minCount) $(SUBREF searching, maxCount) + $(SUBREF searching, minElement) + $(SUBREF searching, maxElement) $(SUBREF searching, minPos) $(SUBREF searching, maxPos) $(SUBREF searching, skipOver) diff --git a/std/algorithm/searching.d b/std/algorithm/searching.d index 41f716bf43b..0ce9afe0976 100644 --- a/std/algorithm/searching.d +++ b/std/algorithm/searching.d @@ -59,6 +59,12 @@ $(T2 minCount, $(D minCount([2, 1, 1, 4, 1])) returns $(D tuple(1, 3)).) $(T2 maxCount, $(D maxCount([2, 4, 1, 4, 1])) returns $(D tuple(4, 2)).) +$(T2 minElement, + Selects the minimal element of a range. + `minElement([3, 4, 1, 2])` returns `1`.) +$(T2 maxElement, + Selects the maximal element of a range. + `maxElement([3, 4, 1, 2])` returns `4`.) $(T2 minPos, $(D minPos([2, 3, 1, 3, 4, 1])) returns the subrange $(D [1, 3, 4, 1]), i.e., positions the range at the first occurrence of its minimal @@ -1204,6 +1210,103 @@ bool endsWith(alias pred, R)(R doesThisEnd) } } +/** +Iterates the passed range and selects the extreme element with `less`. +If the extreme element occurs multiple time, the first occurrence will be +returned. + +Params: + map = custom accessor for the comparison key + selector = custom mapping for the extrema selection + seed = custom seed to use as initial element + r = Range from which the extreme value will be selected + +Returns: + 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) + if (isInputRange!Range && !isInfinite!Range) +in +{ + assert(!r.empty, "r is an empty range"); +} +body +{ + alias mapFun = unaryFun!map; + alias Element = ElementType!Range; + Unqual!Element seed = r.front; + r.popFront(); + return extremum!(map, selector)(r, seed); +} + +private auto extremum(alias map = "a", alias selector = "a < b", Range, + RangeElementType = ElementType!Range) + (Range r, RangeElementType seedElement) + if (isInputRange!Range && !isInfinite!Range && + !is(CommonType!(ElementType!Range, RangeElementType) == void)) +{ + alias mapFun = unaryFun!map; + alias selectorFun = binaryFun!selector; + + alias Element = ElementType!Range; + alias CommonElement = CommonType!(Element, RangeElementType); + alias MapType = Unqual!(typeof(mapFun(CommonElement.init))); + + Unqual!CommonElement extremeElement = seedElement; + MapType extremeElementMapped = mapFun(extremeElement); + + static if (isRandomAccessRange!Range && hasLength!Range) + { + foreach (const i; 0 .. r.length) + { + MapType mapElement = mapFun(r[i]); + if (selectorFun(mapElement, extremeElementMapped)) + { + extremeElement = r[i]; + extremeElementMapped = mapElement; + } + } + } + else + { + while (!r.empty) + { + MapType mapElement = mapFun(r.front); + if (selectorFun(mapElement, extremeElementMapped)) + { + extremeElement = r.front; + extremeElementMapped = mapElement; + } + r.popFront(); + } + } + return extremeElement; +} + +@safe pure nothrow unittest +{ + // 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[1]" == [1, 2]); + + // 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[1]", "a > b") == [0, 4]); +} + +@safe pure nothrow unittest +{ + // allow seeds + int[] arr; + assert(arr.extremum(1) == 1); + + int[][] arr2d; + assert(arr2d.extremum([1]) == [1]); + + // allow seeds of different types (implicit casting) + assert([2, 3, 4].extremum(1.5) == 1.5); +} + // find /** Finds an individual element in an input range. Elements of $(D @@ -2960,6 +3063,188 @@ unittest } } +/** +Iterates the passed range and returns the minimal element. +A custom mapping function can be passed to `map`. + +Complexity: O(n) + Exactly `n - 1` comparisons are needed. + +Params: + map = custom accessor for the comparison key + r = range from which the minimal element will be selected + seed = custom seed to use as initial element + +Returns: The minimal element of the passed-in range. + +See_Also: + $(XREF algorithm, comparison, min) +*/ +auto minElement(alias map = "a", Range)(Range r) + if (isInputRange!Range && !isInfinite!Range) +{ + return extremum!map(r); +} + +/// ditto +auto minElement(alias map = "a", Range, RangeElementType = ElementType!Range) + (Range r, RangeElementType seed) + if (isInputRange!Range && !isInfinite!Range && + !is(CommonType!(ElementType!Range, RangeElementType) == void)) +{ + return extremum!map(r, seed); +} + +/// +@safe pure unittest +{ + import std.range: enumerate; + + assert([2, 1, 4, 3].minElement == 1); + + // allows to get the index of an element too + assert([5, 3, 7, 9].enumerate.minElement!"a.value" == tuple(1, 3)); + + // any custom accessor can be passed + assert([[0, 4], [1, 2]].minElement!"a[1]" == [1, 2]); + + // can be seeded + int[] arr; + assert(arr.minElement(1) == 1); +} + +@safe pure unittest +{ + import std.range: enumerate, iota; + // supports mapping + assert([3, 4, 5, 1, 2].enumerate.minElement!"a.value" == tuple(3, 1)); + assert([5, 2, 4].enumerate.minElement!"a.value" == tuple(1, 2)); + + // forward ranges + assert(iota(1, 5).minElement() == 1); + assert(iota(2, 5).enumerate.minElement!"a.value" == tuple(0, 2)); + + // should work with const + const(int)[] immArr = [2, 1, 3]; + assert(immArr.minElement == 1); + + // should work with immutable + immutable(int)[] immArr2 = [2, 1, 3]; + assert(immArr2.minElement == 1); + + // with strings + assert(["b", "a", "c"].minElement == "a"); + + // with all dummy ranges + import std.internal.test.dummyrange; + foreach (DummyType; AllDummyRanges) + { + DummyType d; + assert(d.minElement == 1); + } +} + +@nogc @safe nothrow pure unittest +{ + static immutable arr = [7, 3, 4, 2, 1, 8]; + assert(arr.minElement == 1); + + static immutable arr2d = [[1, 9], [3, 1], [4, 2]]; + assert(arr2d.minElement!"a[1]" == arr2d[1]); +} + +/** +Iterates the passed range and returns the maximal element. +A custom mapping function can be passed to `map`. + +Complexity: + Exactly `n - 1` comparisons are needed. + +Params: + map = custom accessor for the comparison key + r = range from which the maximum will be selected + seed = custom seed to use as initial element + +Returns: The maximal element of the passed-in range. + +See_Also: + $(XREF algorithm, comparison, max) +*/ +auto maxElement(alias map = "a", Range)(Range r) + if (isInputRange!Range && !isInfinite!Range && + !is(CommonType!(ElementType!Range, RangeElementType) == void)) +{ + return extremum!(map, "a > b")(r); +} + +/// ditto +auto maxElement(alias map = "a", Range, RangeElementType = ElementType!Range) + (Range r, RangeElementType seed) + if (isInputRange!Range && !isInfinite!Range) +{ + return extremum!(map, "a > b")(r, seed); +} + +/// +@safe pure unittest +{ + import std.range: enumerate; + assert([2, 1, 4, 3].maxElement == 4); + + // allows to get the index of an element too + assert([2, 1, 4, 3].enumerate.maxElement!"a.value" == tuple(2, 4)); + + // any custom accessor can be passed + assert([[0, 4], [1, 2]].maxElement!"a[1]" == [0, 4]); + + // can be seeded + int[] arr; + assert(arr.minElement(1) == 1); +} + +@safe pure unittest +{ + import std.range: enumerate, iota; + + // supports mapping + assert([3, 4, 5, 1, 2].enumerate.maxElement!"a.value" == tuple(2, 5)); + assert([5, 2, 4].enumerate.maxElement!"a.value" == tuple(0, 5)); + + // forward ranges + assert(iota(1, 5).maxElement() == 4); + assert(iota(2, 5).enumerate.maxElement!"a.value" == tuple(2, 4)); + assert(iota(4, 14).enumerate.maxElement!"a.value" == tuple(9, 13)); + + // should work with const + const(int)[] immArr = [2, 3, 1]; + assert(immArr.maxElement == 3); + + // should work with immutable + immutable(int)[] immArr2 = [2, 3, 1]; + assert(immArr2.maxElement == 3); + + // with strings + assert(["a", "c", "b"].maxElement == "c"); + + // with all dummy ranges + import std.internal.test.dummyrange; + foreach (DummyType; AllDummyRanges) + { + DummyType d; + assert(d.maxElement == 10); + } +} + +@nogc @safe nothrow pure unittest +{ + static immutable arr = [7, 3, 8, 2, 1, 4]; + assert(arr.maxElement == 8); + + static immutable arr2d = [[1, 3], [3, 9], [4, 2]]; + assert(arr2d.maxElement!"a[1]" == arr2d[1]); +} + + // minPos /** Computes a subrange of `range` starting at the first occurrence of `range`'s