Sometimes I want to find the first element in a collection that satisfies some predicate f
. The collection may not be (O(1)) indexable, so findfirst
then lookup is not ideal.
I am looking for something like
function firstmatching(f, itr)
state = ()
while true
y = iterate(itr, state...)
y ≡ nothing && return nothing
elt = first(y)
f(elt) && return Some(elt)
state = Base.tail(y)
end
end
Eg
julia> firstmatching(isodd, (2, 4, 5))
Some(5)
julia> firstmatching(x -> last(x) ≡ :a, [1 => :b, 2 => :c, 3 => :a])
Some(3 => :a)
Does something similar exist in Base
, or a commonly used package?
How about first(Iterators.filter(f, itr))
?
I thought about that too, but besides being kind of complicated,
julia> first(Iterators.filter(isodd, 2:2:10))
ERROR: ArgumentError: collection must be non-empty
Stacktrace:
[1] first(::Base.Iterators.Filter{typeof(isodd),StepRange{Int64,Int64}}) at ./abstractarray.jl:289
[2] top-level scope at none:0
Complicated? I feel like this is precisely the kind of thing Iterators.filter
was made for. To support nothing
, you could do:
function firstmatching2(f, itr)
it = iterate(Iterators.filter(f, itr))
it ≡ nothing ? nothing : it[1]
end
The advantage of reusing existing code is that you get maintenance, testing and (usually) a decent quality implementation for free. For example:
julia> A = [0:2:1000000; 1];
julia> @btime firstmatching(isodd, A)
34.428 ms (1999497 allocations: 38.14 MiB)
Some(1)
julia> @btime firstmatching2(isodd, A)
334.707 μs (2 allocations: 48 bytes)
1
I’m sure you can fix this in your implementation (if it matters to you), but if you reuse existing code, you don’t have to worry about these things.
1 Like