If you would probably do a lot of these computations it would be best to wrap it inside a function:
julia> function compute(array, cond1::Function, cond2::Function)
m = maximum(array[cond1.(array) .&& cond2.(array)])
f = findmax(elem -> isequal(elem, m), array)
compute (generic function with 1 method)
julia> compute(rand(Int, 100), iseven, signbit)
No specific reason, just small overheads here and there that weren’t removed by the compiler.
Sprinkled a few inlines and boundschecks — now both variants are faster, and times are close to each other:
In this comment I probe the idea that maybe this is a mistake.
Namely, the semantics of lazy Iterators.filter, which ignores the original collection’s indices, are based on an understanding of what “filtering” means informed by eager Base.filter, which conflates the idea of filtering with the act of collecting. However, if our starting point had instead been lazy filter, maybe this decision would have gone the other way—maybe the lazy filter would preserve the original collection’s indices.
Although, maybe the name filter is too tainted to go back now. That said, it’s not an especially informative name anyway—it doesn’t specify whether to keep or to discard the values for which the predicate returns true. In electrical engineering, we use the word pass to describe filters (e.g. band-pass filter, low-pass filter, etc.), but I’m finding myself enamored by @jar1’s suggestion of keep.