And before someone corrects me, an even less embarrassing variant might be:
proc rangeAt*[T](xs: openArray[T]): tuple[minAt, maxAt: int] =
if xs.len < 1: return (-1, -1) # raise?
for i in 1 ..< xs.len: # note tuple ints init to zero
if xs[result.minAt] < xs[i]: result.minAt = i
if xs[result.minAt] > xs[i]: result.minAt = i
Run
with then `let (minAt, maxAt) = strs.rangeAt` and tracking `(min|max)` ->
`strs[(min|max)At]` changes in the string comparison for the prefix length
algo. Some paired-up value oriented interface for those who want it would also
make sense:
proc range*[T](xs: openArray[T]): tuple[min, max: T] {.inline.} =
if xs.len < 1: return # raise?
let (minAt, maxAt) = xs.rangeAt
result.min = xs[minAt]
result.max = xs[maxAt]
Run
in which case maybe no change to the prefix len algo is needed.
While off the topic of the prefix calculation, it perhaps bears noting that
SSE/AVX calculations can sometimes speed up the value-oriented versions of
these range computations over arrays of CPU supported types like
`openArray[float32]` by large factors - 24x even. Basically branchless and
batching vector min/max instructions can be leveraged, but I am unaware of CPUs
supporting "minAt" type vector instructions. Recent versions of gcc can
recognize these min/max type value sweeps and correctly generate optimized
code, but such pattern recognition can often fail as the code grows just a
little more complex.