From d89fd45b9ef3bef4e36c379f3ad277f8809d6d16 Mon Sep 17 00:00:00 2001 From: Amjad Ben Hedhili Date: Tue, 6 May 2025 13:09:03 +0100 Subject: [PATCH] Add min/max overloads with comparison functions (#23595) `min`, `max`, `minmax`, `minIndex` and `maxIndex` (cherry picked from commit 59ceff4f1afdbc35cfc1dd679ca31369e71b3873) --- changelog.md | 2 +- lib/pure/collections/sequtils.nim | 48 ++++++++++++++++++++++++++++++- 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/changelog.md b/changelog.md index 6529a26f1f..dc225844b8 100644 --- a/changelog.md +++ b/changelog.md @@ -34,7 +34,7 @@ errors. [//]: # "Changes:" - `std/math` The `^` symbol now supports floating-point as exponent in addition to the Natural type. - +- `min`, `max`, and `sequtils`' `minIndex`, `maxIndex` and `minmax` for `openArray`s now accept a comparison function. - `system.substr` implementation now uses `copymem` (wrapped C `memcpy`) for copying data, if available at compilation. - `system.newStringUninit` is now considered free of side-effects allowing it to be used with `--experimental:strictFuncs`. diff --git a/lib/pure/collections/sequtils.nim b/lib/pure/collections/sequtils.nim index 57d3a405fe..97ebf367cc 100644 --- a/lib/pure/collections/sequtils.nim +++ b/lib/pure/collections/sequtils.nim @@ -231,6 +231,18 @@ func deduplicate*[T](s: openArray[T], isSorted: bool = false): seq[T] = for itm in items(s): if not result.contains(itm): result.add(itm) +proc min*[T](x: openArray[T], cmp: proc(a, b: T): int): T {.effectsOf: cmp.} = + ## The minimum value of `x`. + result = x[0] + for i in 1..high(x): + if cmp(x[i], result) < 0: result = x[i] + +proc max*[T](x: openArray[T], cmp: proc(a, b: T): int): T {.effectsOf: cmp.} = + ## The maximum value of `x`. + result = x[0] + for i in 1..high(x): + if cmp(result, x[i]) < 0: result = x[i] + func minIndex*[T](s: openArray[T]): int {.since: (1, 1).} = ## Returns the index of the minimum value of `s`. ## `T` needs to have a `<` operator. @@ -248,6 +260,20 @@ func minIndex*[T](s: openArray[T]): int {.since: (1, 1).} = for i in 1..high(s): if s[i] < s[result]: result = i +func minIndex*[T](s: openArray[T], cmp: proc(a, b: T): int): int {.effectsOf: cmp.} = + ## Returns the index of the minimum value of `s`. + runnableExamples: + import std/sugar + + let s1 = @["foo","bar", "hello"] + let s2 = @[2..4, 1..3, 6..10] + assert minIndex(s1, proc (a, b: string): int = a.len - b.len) == 0 + assert minIndex(s2, (a, b) => a.a - b.a) == 1 + + for i in 1..high(s): + if cmp(s[i], s[result]) < 0: result = i + + func maxIndex*[T](s: openArray[T]): int {.since: (1, 1).} = ## Returns the index of the maximum value of `s`. ## `T` needs to have a `<` operator. @@ -265,15 +291,35 @@ func maxIndex*[T](s: openArray[T]): int {.since: (1, 1).} = for i in 1..high(s): if s[i] > s[result]: result = i +func maxIndex*[T](s: openArray[T], cmp: proc(a, b: T): int): int {.effectsOf: cmp.} = + ## Returns the index of the maximum value of `s`. + runnableExamples: + import std/sugar + + let s1 = @["foo","bar", "hello"] + let s2 = @[2..4, 1..3, 6..10] + assert maxIndex(s1, proc (a, b: string): int = a.len - b.len) == 2 + assert maxIndex(s2, (a, b) => a.a - b.a) == 2 + + for i in 1..high(s): + if cmp(s[result], s[i]) < 0: result = i + func minmax*[T](x: openArray[T]): (T, T) = ## The minimum and maximum values of `x`. `T` needs to have a `<` operator. var l = x[0] var h = x[0] for i in 1..high(x): if x[i] < l: l = x[i] - if h < x[i]: h = x[i] + elif h < x[i]: h = x[i] result = (l, h) +func minmax*[T](x: openArray[T], cmp: proc(a, b: T): int): (T, T) {.effectsOf: cmp.} = + ## The minimum and maximum values of `x`. + result = (x[0], x[0]) + for i in 1..high(x): + if cmp(x[i], result[0]) < 0: result[0] = x[i] + elif cmp(result[1], x[i]) < 0: result[1] = x[i] + template zipImpl(s1, s2, retType: untyped): untyped = proc zip*[S, T](s1: openArray[S], s2: openArray[T]): retType =