From 6c7d0e2ef15bb39a7767a605837cd248264e374f Mon Sep 17 00:00:00 2001 From: Nick Treleaven Date: Sat, 25 Mar 2023 12:05:31 +0000 Subject: [PATCH 1/4] [std.algorithm.searching] Add extrema --- std/algorithm/searching.d | 69 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 67 insertions(+), 2 deletions(-) diff --git a/std/algorithm/searching.d b/std/algorithm/searching.d index e0d24359a51..9b2a3c05867 100644 --- a/std/algorithm/searching.d +++ b/std/algorithm/searching.d @@ -34,6 +34,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).)) @@ -3684,7 +3685,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) @@ -3865,7 +3866,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) @@ -4035,6 +4036,70 @@ if (isInputRange!Range && !isInfinite!Range && assert(maxElement(arr) == S(145)); } +/** Returns an array of the minimum and maximum element in `r`. + * Makes `< 3n/2` comparisons. + */ +// TODO alias map = a => a +ElementType!Range[2] extrema(Range)(Range r) +if (isRandomAccessRange!Range && hasLength!Range) +in (!r.empty) +{ + 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; +} + +unittest +{ + assert(extrema([5,2,9,4,1]) == [1, 9]); + 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]); + + version (StdRandomTests) + foreach (i; 0..1000) + { + import std.random, std.range; + 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 From 762b0b4881ce55a6ead760ceb7f56c219c7ee540 Mon Sep 17 00:00:00 2001 From: Nick Treleaven Date: Sat, 25 Mar 2023 16:00:07 +0000 Subject: [PATCH 2/4] Minor tweaks --- std/algorithm/searching.d | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/std/algorithm/searching.d b/std/algorithm/searching.d index 9b2a3c05867..8cc25f75616 100644 --- a/std/algorithm/searching.d +++ b/std/algorithm/searching.d @@ -4037,7 +4037,9 @@ if (isInputRange!Range && !isInfinite!Range && } /** Returns an array of the minimum and maximum element in `r`. - * Makes `< 3n/2` comparisons. + * 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) @@ -4082,15 +4084,20 @@ in (!r.empty) return result; } -unittest +/// +@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]); version (StdRandomTests) - foreach (i; 0..1000) + foreach (i; 0 .. 1000) { import std.random, std.range; auto arr = generate!(() => uniform(0, 100)).takeExactly(uniform(1, 10)).array; From 92f677f2cfe44c5d9c0c9f2aa3e441ade90166e8 Mon Sep 17 00:00:00 2001 From: Nick Treleaven Date: Sun, 26 Mar 2023 12:48:05 +0100 Subject: [PATCH 3/4] Only require input range --- std/algorithm/searching.d | 108 +++++++++++++++++++++++++++----------- 1 file changed, 78 insertions(+), 30 deletions(-) diff --git a/std/algorithm/searching.d b/std/algorithm/searching.d index 8cc25f75616..2e9d6f52638 100644 --- a/std/algorithm/searching.d +++ b/std/algorithm/searching.d @@ -4043,45 +4043,89 @@ if (isInputRange!Range && !isInfinite!Range && */ // TODO alias map = a => a ElementType!Range[2] extrema(Range)(Range r) -if (isRandomAccessRange!Range && hasLength!Range) +if (isInputRange!Range && !isInfinite!Range) in (!r.empty) { - 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) + static if (isRandomAccessRange!Range && hasLength!Range) { - // save work - if (r[i] < r[i+1]) + if (r.length == 1) + return [r[0], r[0]]; + + typeof(return) result; + size_t i; + if (r.length & 1) // odd { - if (r[i] < result[0]) - result[0] = r[i]; - if (r[i+1] > result[1]) - result[1] = r[i+1]; + result = [r[0], r[0]]; + i = 1; } else { - if (r[i+1] < result[0]) - result[0] = r[i+1]; - if (r[i] > result[1]) - result[1] = r[i]; + 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; + } } } - return result; } /// @@ -4096,6 +4140,10 @@ in (!r.empty) assert(extrema([1,5,3,2]) == [1, 5]); assert(extrema([2,3,3,2]) == [2, 3]); + import std.internal.test.dummyrange; + DummyRange!(ReturnBy.Reference, Length.No, RangeType.Input) r; + assert(r.extrema == [1u, 10u]); + version (StdRandomTests) foreach (i; 0 .. 1000) { From 347e4152ccc7440fdd422b084735d4205e24183e Mon Sep 17 00:00:00 2001 From: Nick Treleaven Date: Sun, 26 Mar 2023 19:16:28 +0100 Subject: [PATCH 4/4] Extend tests --- std/algorithm/searching.d | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/std/algorithm/searching.d b/std/algorithm/searching.d index 2e9d6f52638..924ea1c6b64 100644 --- a/std/algorithm/searching.d +++ b/std/algorithm/searching.d @@ -4140,14 +4140,21 @@ in (!r.empty) 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; - DummyRange!(ReturnBy.Reference, Length.No, RangeType.Input) r; - assert(r.extrema == [1u, 10u]); + foreach (DummyType; AllDummyRanges) + { + DummyType d; + assert(d.extrema == [1, 10]); + } version (StdRandomTests) foreach (i; 0 .. 1000) { - import std.random, std.range; + import std.random; auto arr = generate!(() => uniform(0, 100)).takeExactly(uniform(1, 10)).array; auto result = arr.extrema; assert(result[0] == arr.minElement);