Generic code for elementwise minimum

I am wondering how to program a generic elementwise minimum of, say, vectors, with the following in mind: the result from an AbstractVector{T} is a T, eg SVector and Vector would be preserved.

Data-generating MWE:

using StaticArrays
v = [randn(3) for _ in 1:10]           # Vector
s = [SVector(v...) for v in v]         # SVector
m = minimum(reduce(hcat, v); dims = 2) # for testing

Eg for elementwise_min(v) I want a Vector{Float64}, and for elementwise_min(s) an SArray{Tuple{3},Float64,1,3}.

1 Like

min.(v...) works but is probably a bad idea when v is large.

Don’t you want an SArray{Tuple{10},Float64,1,10}?
Anyway that would involve getting the eltype of your input and, depending on your type, calculating the type parameters (e.g. for the vector of StaticArrays the length is not known at compile time and thus the parameter of the resulting static vector is not known).

I don’t think it gets much better than applying a function elementwise and converting the resulting array explicitly with a helper-function, i.e.

julia> foo(f,xs) = myconv(eltype(xs),map(f,xs))
       myconv(::Type{<:SArray}, xs) = SVector(xs...)
       myconv(::Type{<:Array}, xs) = xs

but would be happy to learn otherwise.

This gave me an idea:

julia> elementwise_f(f, v) = reduce((x, y) -> f.(x, y), v)
elementwise_f (generic function with 1 method)

julia> elementwise_f(min, v) == m

julia> elementwise_f(min, s) == m



Why is this a bad idea when v is large?

splatting is inefficient for large sizes.


I thought min.(...) was broadcasting?

min.(x, y) will broadcast min over x and y. min.(v...) will first splat v into the arguments to min and then broadcast over those splatted arguments. It’s the splatting that is extremely inefficient for large sizes.

julia> let v = [randn(3) for _ in 1:100]
           @btime min.($v...)
           @btime reduce((x, y) -> min.(x, y), $v)
  1.674 ms (33681 allocations: 1.44 MiB)
  5.292 μs (99 allocations: 10.83 KiB)

Ah, gotcha. Thanks for the explanation!