Coding pattern for "min with side effects"

Many algorithms require computing min(a,b) and then executing some side effects depending on whether a < b. I’m struggling to come up with a good coding pattern for this type of logic.

The problem is best described by means of an example. Consider the following function for finding the smallest element in a vector v.

function minimum(v)
   @assert !isempty(v)
    x = v[1]
    for i = 2:length(v)
       x = min(x, v[i])
    end
    return x
end

If in addition to finding the minimum we also want to find the index of the minimum, then this function has to be changed as follows.

function findmin(v)
   @assert !isempty(v)
    x = v[1]
    j = 1
    for i = 2:length(v)
        if v[i] < x
            x = v[i]
            j = i
        end
    end
    return x,j
end

My beef with this type of code is that v[i] occurs both in the if statement and the line just after the if statement. I believe this inefficient (repeated index lookups can’t be optimised by the compiler AFAIK), and if the statement is more complicated than simply v[i], then repeating the same complicated statement is a likely source of typos. Of course, these problems could be avoided by introducing a temporary variable, but temporaries are often ugly and make the code harder to read. So my question is, is there a way to write this logic without repeating v[i] and without introducing a temporary?


I’m aware that this is a very OCD type of question and that in the real world you would just do whatever works and move on. But I thought it could be instructive and fun to hear what other people think about this.

you have a few options but no clear winner if you only use v[i] twice…

  1. make vi = v[i] immediate after for i = 2:length(v).
  2. use enumerate
2 Likes
function minimum(v)
    m = Inf
    i = 0
    for (k, e) in enumerate(v)
        if e ≥ m
            continue
        end
        m = e
        i = k
    end
    return (m, i)
end

This saves you linear indexing at all, which is good if v is, for instance, not an array.

1 Like

This is not, however, guaranteed to be typestable (if the array is empty or all its elements are >Inf it will return a Float64, otherwise eltype(v)). Possibly a better way is to use

((i, m), it) = Iterators.peel(enumerate(v)) # not sure this nested destructuring works
for (k, e) in it
    ...