Skip to content

Commit ce131c1

Browse files
committed
Add methods of first and last accepting a predicate
On zulip, there was a thread about the fact that there's not a good way of finding the first item in a non-indexable iterable without manually writing out the for-loop. One possibility is using the existing first function composed with Iterators.filter: first(Iterators.filter(>(5), 1:10)) The problem with this approach is that it requires wrapping in a try block if the predicate not being satisfied is a possibility (Iterators.filter returns an empty collection). There should be a way of expressing this without using exception-handling as control flow or writing the for-loop manually. Ideally, I think first and last should just return Union{Some{T},Nothing} always, but that would be a breaking change. So I see two obvious alternatives: 1. This commit adds an optional predicate argument to first and last which is mapped over the collection and the first/last value satisfying predicate is returned. 2. Add a Iterators.first/Iterators.last which are identical to the versions in Base except that when successful they return Some{T} and when unsuccesful return Nothing
1 parent 0fd5833 commit ce131c1

File tree

2 files changed

+75
-0
lines changed

2 files changed

+75
-0
lines changed

base/abstractarray.jl

+65
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,34 @@ function first(itr)
343343
x[1]
344344
end
345345

346+
"""
347+
first(predicate, coll)
348+
349+
Get the first element of `coll` satisfying `predicate` wrapped in `Some`.
350+
351+
If no element of `coll` satisfies `predicate`, return `nothing`.
352+
353+
# Examples
354+
```jldoctest
355+
julia> first(>(5), 1:10)
356+
Some(6)
357+
358+
julia> first(isodd, 2:2:10) |> isnothing
359+
true
360+
361+
julia> first(iseven, [5; 3; 4; 2; 6; 8]) |> something
362+
4
363+
364+
julia> something(first(>(10), 1:10), 0)
365+
0
366+
```
367+
"""
368+
function first(predicate, itr)
369+
for x in itr
370+
predicate(x) && return Some(x)
371+
end
372+
end
373+
346374
"""
347375
first(itr, n::Integer)
348376
@@ -388,6 +416,43 @@ julia> last([1; 2; 3; 4])
388416
"""
389417
last(a) = a[end]
390418

419+
"""
420+
last(predicate, coll)
421+
422+
Get the last element of `coll` satisfying `predicate` wrapped in `Some`.
423+
424+
If no element of `coll` satisfies `predicate`, return `nothing`.
425+
426+
# Examples
427+
```jldoctest
428+
julia> last(<(5), 1:10)
429+
Some(4)
430+
431+
julia> last(isodd, 2:2:10) |> isnothing
432+
true
433+
434+
julia> last(iseven, [5; 3; 4; 2; 6; 9]) |> something
435+
6
436+
437+
julia> last(first(>(10), 1:10), 0)
438+
0
439+
```
440+
"""
441+
function last(predicate, itr)
442+
out = nothing
443+
for x in itr
444+
out = ifelse(predicate(x), Some(x), out)
445+
end
446+
out
447+
end
448+
449+
# faster version for arrays
450+
function last(predicate, a::AbstractArray)
451+
@inbounds for i in reverse(eachindex(a))
452+
predicate(a[i]) && return Some(a[i])
453+
end
454+
end
455+
391456
"""
392457
last(itr, n::Integer)
393458

test/abstractarray.jl

+10
Original file line numberDiff line numberDiff line change
@@ -1141,3 +1141,13 @@ end
11411141
@test last(itr, 1) == [itr[end]]
11421142
@test_throws ArgumentError last(itr, -6)
11431143
end
1144+
1145+
@testset "first/last element satisfying predicate of $(typeof(itr))" for itr in (1:9,
1146+
collect(1:9),
1147+
reshape(1:9, (3, 3)),
1148+
ntuple(identity, 9))
1149+
@test first(>(5), itr) |> something == 6
1150+
@test last(<(5), itr) |> something == 4
1151+
@test first(>(9), itr) == nothing
1152+
@test last(>(9), itr) == nothing
1153+
end

0 commit comments

Comments
 (0)