From 5ba279276ea98e59e36faa6cbf71c10e12b43124 Mon Sep 17 00:00:00 2001 From: Jacek Sieka Date: Tue, 9 Sep 2025 20:05:12 +0200 Subject: [PATCH] sequtils: `findIt` (#25134) Complements `anyIt`, `find`, etc, plugging an odd gap in the `xxxIt` family of functions --- lib/pure/collections/sequtils.nim | 62 ++++++++++++++++++++----------- tests/stdlib/tsequtils.nim | 11 ++++++ 2 files changed, 52 insertions(+), 21 deletions(-) diff --git a/lib/pure/collections/sequtils.nim b/lib/pure/collections/sequtils.nim index 42d54c8392..202f5a3b1c 100644 --- a/lib/pure/collections/sequtils.nim +++ b/lib/pure/collections/sequtils.nim @@ -116,6 +116,11 @@ macro evalOnceAs(expAlias, exp: untyped, newProc(name = genSym(nskTemplate, $expAlias), params = [getType(untyped)], body = val, procType = nnkTemplateDef)) +template unCheckedInc(x) = + {.push overflowChecks: off.} + inc(x) + {.pop.} + func concat*[T](seqs: varargs[seq[T]]): seq[T] = ## Takes several sequences' items and returns them inside a new sequence. ## All sequences must be of the same type. @@ -139,7 +144,7 @@ func concat*[T](seqs: varargs[seq[T]]): seq[T] = for s in items(seqs): for itm in items(s): result[i] = itm - inc(i) + unCheckedInc(i) func addUnique*[T](s: var seq[T], x: sink T) = ## Adds `x` to the container `s` if it is not already present. @@ -170,7 +175,7 @@ func count*[T](s: openArray[T], x: T): int = result = 0 for itm in items(s): if itm == x: - inc result + unCheckedInc result func cycle*[T](s: openArray[T], n: Natural): seq[T] = ## Returns a new sequence with the items of the container `s` repeated @@ -188,7 +193,7 @@ func cycle*[T](s: openArray[T], n: Natural): seq[T] = for x in 0 ..< n: for e in s: result[o] = e - inc o + unCheckedInc o proc repeat*[T](x: T, n: Natural): seq[T] = ## Returns a new sequence with the item `x` repeated `n` times. @@ -321,6 +326,26 @@ func minmax*[T](x: openArray[T], cmp: proc(a, b: T): int): (T, T) {.effectsOf: c elif cmp(result[1], x[i]) < 0: result[1] = x[i] +template findIt*(s, predicate: untyped): int = + ## Iterates through a container and returns the index of the first item that + ## fulfills the predicate, or -1 + ## + ## Unlike the `find`, the predicate needs to be an expression using + ## the `it` variable for testing, like: `findIt([3, 2, 1], it == 2)`. + var + res = -1 + i = 0 + + # We must use items here since both `find` and `anyIt` are defined in terms + # of `items` + # (and not `pairs`) + for it {.inject.} in items(s): + if predicate: + res = i + break + unCheckedInc(i) + res + template zipImpl(s1, s2, retType: untyped): untyped = proc zip*[S, T](s1: openArray[S], s2: openArray[T]): retType = ## Returns a new sequence with a combination of the two input containers. @@ -417,7 +442,7 @@ func distribute*[T](s: seq[T], num: Positive, spread = true): seq[seq[T]] = if extra == 0 or spread == false: # Use an algorithm which overcounts the stride and minimizes reading limits. - if extra > 0: inc(stride) + if extra > 0: unCheckedInc(stride) for i in 0 ..< num: result[i] = newSeq[T]() for g in first ..< min(s.len, first + stride): @@ -429,7 +454,7 @@ func distribute*[T](s: seq[T], num: Positive, spread = true): seq[seq[T]] = last = first + stride if extra > 0: extra -= 1 - inc(last) + unCheckedInc(last) result[i] = newSeq[T]() for g in first ..< last: result[i].add(s[g]) @@ -586,7 +611,7 @@ proc keepIf*[T](s: var seq[T], pred: proc(x: T): bool {.closure.}) s[pos] = move(s[i]) else: shallowCopy(s[pos], s[i]) - inc(pos) + unCheckedInc(pos) setLen(s, pos) func delete*[T](s: var seq[T]; slice: Slice[int]) = @@ -617,8 +642,8 @@ func delete*[T](s: var seq[T]; slice: Slice[int]) = s[i] = move(s[j]) else: s[i].shallowCopy(s[j]) - inc(i) - inc(j) + unCheckedInc(i) + unCheckedInc(j) setLen(s, newLen) when nimvm: defaultImpl() else: @@ -649,8 +674,8 @@ func delete*[T](s: var seq[T]; first, last: Natural) {.deprecated: "use `delete( s[i] = move(s[j]) else: s[i].shallowCopy(s[j]) - inc(i) - inc(j) + unCheckedInc(i) + unCheckedInc(j) setLen(s, newLen) func insert*[T](dest: var seq[T], src: openArray[T], pos = 0) = @@ -681,10 +706,10 @@ func insert*[T](dest: var seq[T], src: openArray[T], pos = 0) = dec(i) dec(j) # Insert items from `dest` into `dest` at `pos` - inc(j) + unCheckedInc(j) for item in src: dest[j] = item - inc(j) + unCheckedInc(j) template filterIt*(s, pred: untyped): untyped = @@ -743,7 +768,7 @@ template keepItIf*(varSeq: seq, pred: untyped) = varSeq[pos] = move(varSeq[i]) else: shallowCopy(varSeq[pos], varSeq[i]) - inc(pos) + unCheckedInc(pos) setLen(varSeq, pos) since (1, 1): @@ -842,12 +867,7 @@ template anyIt*(s, pred: untyped): bool = assert numbers.anyIt(it > 8) == true assert numbers.anyIt(it > 9) == false - var result = false - for it {.inject.} in items(s): - if pred: - result = true - break - result + findIt(s, pred) != -1 template toSeq1(s: not iterator): untyped = # overload for typed but not iterator @@ -875,7 +895,7 @@ template toSeq2(iter: iterator): untyped = var result = newSeq[typeof(iter2)](iter2.len) for x in iter2: result[i] = x - inc i + unCheckedInc i result else: type OutType = typeof(iter2()) @@ -920,7 +940,7 @@ template toSeq*(iter: untyped): untyped = var i = 0 for x in iter2: result[i] = x - inc i + unCheckedInc i result else: var result: seq[typeof(iter)] = @[] diff --git a/tests/stdlib/tsequtils.nim b/tests/stdlib/tsequtils.nim index df0fb1610a..027f9b1195 100644 --- a/tests/stdlib/tsequtils.nim +++ b/tests/stdlib/tsequtils.nim @@ -258,6 +258,17 @@ block: # any doAssert any(anumbers, proc (x: int): bool = return x > 8) == true doAssert any(anumbers, proc (x: int): bool = return x > 9) == false +block: # findIt + let + numbers = @[1, 4, 5, 8, 9, 7, 4] + anumbers = [1, 4, 5, 8, 9, 7, 4] + len0seq: seq[int] = @[] + doAssert findIt(numbers, it == 4) == 1 + doAssert findIt(numbers, it > 9) == -1 + doAssert findIt(len0seq, true) == -1 + doAssert findIt(anumbers, it > 8) == 4 + doAssert findIt(anumbers, it > 9) == -1 + block: # anyIt let numbers = @[1, 4, 5, 8, 9, 7, 4]