Hey, I'm a Nim beginner and i would like to know if there is a smarter and faster way to do the following. I'm trying to find the minimum of a sequence as the array wchose component are whose components are the minimum of the components of the elements of the sequence.
What I am doing is creating new sequences (x, y, and z) that pertain to the individual components and then I use the default min procedure. In my opinion, this is stupid and not optimal in terms of memory usage and efficiency. Do you have any advice for me? Thanks
type
Vec*[N: static[int], V] = array[N, V]
Vec3*[V] = Vec[3, V]
var
a = Vec3[int]([1, 2, 3])
b = Vec3[int]([-1, 2, 5])
c = Vec3[int]([1, -2, 4])
s = @[a, b, c]
var
x: seq[int]
y: seq[int]
z: seq[int]
for i in 0..<s.len:
x.add(s[i][0])
y.add(s[i][1])
z.add(s[i][2])
echo x # 1, -1, 1
echo y # -1, 2, 5
echo z # 1, -2, 4
echo Vec3[int]([min(x), min(y), min(z)]) #(-1, -2, 3)
Personally I would just use a single array to store the output values because they don't interact with each other.
type
Vec*[N: static[int], V] = array[N, V]
Vec3*[V] = Vec[3, V]
var
a = Vec3[int]([1, 2, 3])
b = Vec3[int]([-1, 2, 5])
c = Vec3[int]([1, -2, 4])
s = @[a, b, c]
proc min[T](vals: varargs[Vec3[T]]): Vec3[T] =
result = vals[0]
for v in vals:
result[0] = min(v[0], result[0])
result[1] = min(v[1], result[1])
result[2] = min(v[2], result[2])
echo min(s)
echo min(a, b, c)
My main two issues with this is that the first check is redundant, but I'd argue it's ok because it makes the code a bit cleaner. And that I'm not sure that the proc name is what I would use, but that is easily changable.
You're absolutely right, there's a more efficient way to find the minimums in Nim! Here's how you can achieve the same result without creating temporary sequences:
Using fold:
The fold function in Nim allows you to accumulate a value based on each element in a sequence. Here's how you can use it:
Nim
return result
echo minVecComponents(s) # (-1, -2, 3)
Explanation:
We define a function minVecComponents that takes a sequence of Vec3 elements as input. We initialize result with the minimum value from the first element using min(s[0]). We iterate through the sequence s using a for loop. Inside the loop, we use vec.min to get the minimum value within the current Vec3 element. We update result by comparing it with the current element's minimum using min(result, vec.min). Finally, the function returns the result which contains the minimums for each component. This approach is more efficient because it iterates through the sequence only once and updates the result directly. It avoids creating unnecessary temporary sequences like x, y, and z.
Using a loop with early termination:
Here's another option using a loop with early termination:
Nim
return result
echo minVecComponents(s) # (-1, -2, 3)
Explanation:
We define a similar function minVecComponents. We initialize result with the first element from the sequence. We iterate through the sequence using a nested loop. The outer loop iterates through each Vec3 element in s. The inner loop iterates through the components of the current Vec3. We compare each component with the corresponding value in result. If a smaller value is found, we update result at that index and use break to terminate the inner loop for that element (early termination). Finally, the function returns result with the minimums. This approach avoids creating temporary sequences as well. However, it requires an additional loop compared to fold. Choose the approach that best suits your readability and preference.
const
a = [1, 2, 3]
b = [-1, 2, 5]
c = [1, -2, 4]
s = [a, b, c]
block original:
var x, y, z: seq[int]
for arr in s:
x.add(arr[0])
y.add(arr[1])
z.add(arr[2])
echo [min(x), min(y), min(z)] #(-1, -2, 3)
import std/options
func minByIdx[N: static int; T](seqOfArrays: openArray[array[N, T]]): Option[array[N, T]] =
if seqOfArrays.len == 0: none(array[N, T])
elif seqOfArrays.len == 1: some(seqOfArrays[0])
else:
var res = seqOfArrays[0]
for i in 1..high(seqOfArrays):
for j in 0..<N:
res[j] = min(res[j], seqOfArrays[i][j])
some(res)
echo minByIdx(s).get()
If you have factually supported reasons to worry about performance (the code performs slowly with the real data), I'd look if the compiler really unrolls the inner loop, it probably should. You can write a macro to unroll it or maybe use SIMD if data fits.