Skip to content

Commit f34bdda

Browse files
committed
Create Iterators.first and Iterators.last
Instead of creating a version of Base.first and Base.last accepting a predicate, create functions in the Iterators module. This allows easy composition with Iterators.filter without having to accept a predicate argument.
1 parent 8c425f7 commit f34bdda

File tree

5 files changed

+101
-106
lines changed

5 files changed

+101
-106
lines changed

base/abstractarray.jl

-73
Original file line numberDiff line numberDiff line change
@@ -343,38 +343,6 @@ 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`](@ref).
350-
351-
If no element of `coll` satisfies `predicate`, return `nothing`.
352-
353-
!!! compat "Julia 1.6"
354-
This method was added in Julia 1.6.
355-
356-
# Examples
357-
```jldoctest
358-
julia> first(>(5), 1:10)
359-
Some(6)
360-
361-
julia> isnothing(first(isodd, 2:2:10))
362-
true
363-
364-
julia> something(first(iseven, [5, 3, 4, 2, 6, 8]))
365-
4
366-
367-
julia> something(first(>(10), 1:10), 0)
368-
0
369-
```
370-
"""
371-
function first(predicate, itr)
372-
for x in itr
373-
predicate(x) && return Some(x)
374-
end
375-
return nothing
376-
end
377-
378346
"""
379347
first(itr, n::Integer)
380348
@@ -420,47 +388,6 @@ julia> last([1; 2; 3; 4])
420388
"""
421389
last(a) = a[end]
422390

423-
"""
424-
last(predicate, coll)
425-
426-
Get the last element of `coll` satisfying `predicate` wrapped in [`Some`](@ref).
427-
428-
If no element of `coll` satisfies `predicate`, return `nothing`.
429-
430-
!!! compat "Julia 1.6"
431-
This method was added in Julia 1.6.
432-
433-
# Examples
434-
```jldoctest
435-
julia> last(<(5), 1:10)
436-
Some(4)
437-
438-
julia> isnothing(last(isodd, 2:2:10))
439-
true
440-
441-
julia> something(last(iseven, [5; 3; 4; 2; 6; 9]))
442-
6
443-
444-
julia> something(last(>(10), 1:10), 0)
445-
0
446-
```
447-
"""
448-
function last(predicate, itr)
449-
out = nothing
450-
for x in itr
451-
out = ifelse(predicate(x), Some(x), out)
452-
end
453-
return out
454-
end
455-
456-
# faster version for arrays
457-
function last(predicate, a::AbstractArray)
458-
@inbounds for i in reverse(eachindex(a))
459-
predicate(a[i]) && return Some(a[i])
460-
end
461-
return nothing
462-
end
463-
464391
"""
465392
last(itr, n::Integer)
466393

base/iterators.jl

+87-8
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ using .Base:
1515
LinearIndices, (:), |, +, -, !==, !, <=, <, missing, any, _counttuple
1616

1717
import .Base:
18-
first, last,
1918
isempty, length, size, axes, ndims,
2019
eltype, IteratorSize, IteratorEltype,
2120
haskey, keys, values, pairs,
@@ -102,8 +101,8 @@ length(r::Reverse) = length(r.itr)
102101
size(r::Reverse) = size(r.itr)
103102
IteratorSize(::Type{Reverse{T}}) where {T} = IteratorSize(T)
104103
IteratorEltype(::Type{Reverse{T}}) where {T} = IteratorEltype(T)
105-
last(r::Reverse) = first(r.itr) # the first shall be last
106-
first(r::Reverse) = last(r.itr) # and the last shall be first
104+
Base.last(r::Reverse) = Base.first(r.itr) # the first shall be last
105+
Base.first(r::Reverse) = Base.last(r.itr) # and the last shall be first
107106

108107
# reverse-order array iterators: assumes more-specialized Reverse for eachindex
109108
@propagate_inbounds function iterate(A::Reverse{<:AbstractArray}, state=(reverse(eachindex(A.itr)),))
@@ -986,8 +985,8 @@ iterate(::ProductIterator{Tuple{}}, state) = nothing
986985

987986
@inline isdone(P::ProductIterator) = any(isdone, P.iterators)
988987
@inline function _pisdone(iters, states)
989-
iter1 = first(iters)
990-
done1 = isdone(iter1, first(states)[2]) # check step
988+
iter1 = Base.first(iters)
989+
done1 = isdone(iter1, Base.first(states)[2]) # check step
991990
done1 === true || return done1 # false or missing
992991
done1 = isdone(iter1) # check restart
993992
done1 === true || return done1 # false or missing
@@ -1012,8 +1011,8 @@ end
10121011

10131012
@inline _piterate1(::Tuple{}, ::Tuple{}) = nothing
10141013
@inline function _piterate1(iters, states)
1015-
iter1 = first(iters)
1016-
next = iterate(iter1, first(states)[2])
1014+
iter1 = Base.first(iters)
1015+
next = iterate(iter1, Base.first(states)[2])
10171016
restnext = tail(states)
10181017
if next === nothing
10191018
isdone(iter1) === true && return nothing
@@ -1335,9 +1334,89 @@ only(x::Tuple) = throw(
13351334
ArgumentError("Tuple contains $(length(x)) elements, must contain exactly 1 element")
13361335
)
13371336
only(a::AbstractArray{<:Any, 0}) = @inbounds return a[]
1338-
only(x::NamedTuple{<:Any, <:Tuple{Any}}) = first(x)
1337+
only(x::NamedTuple{<:Any, <:Tuple{Any}}) = Base.first(x)
13391338
only(x::NamedTuple) = throw(
13401339
ArgumentError("NamedTuple contains $(length(x)) elements, must contain exactly 1 element")
13411340
)
13421341

1342+
"""
1343+
Iterators.first(coll)
1344+
1345+
Return the first element of iterable `coll` wrapped in [`Some`](@ref).
1346+
1347+
If `coll` is empty, return `nothing`.
1348+
1349+
See also [`something`](@ref), [`Iterators.last`](@ref).
1350+
1351+
!!! compat "Julia 1.6"
1352+
This function was added in Julia 1.6.
1353+
1354+
# Extended help
1355+
1356+
This differs from [`first`](@ref) by not throwing an error for empty
1357+
iterables. It can be used with [`Iterators.filter`](@ref) to safely
1358+
get the first element of an iterable which matches a condition. With
1359+
[`first`](@ref), this use requires handling collections with no
1360+
elements matching the condition by catching the thrown
1361+
[`BoundsError`](@ref).
1362+
1363+
# Examples
1364+
```jldoctest
1365+
julia> Iterators.first(Iterators.filter(>(5), 1:10))
1366+
Some(6)
1367+
1368+
julia> isnothing(Iterators.first(Iterators.filter(isodd, 2:2:10)))
1369+
true
1370+
1371+
julia> something(Iterators.first(Iterators.filter(iseven, [5, 3, 4, 2, 6, 8])))
1372+
4
1373+
1374+
julia> something(Iterators.first(Iterators.filter(>(10), 1:10)), 0)
1375+
0
1376+
```
1377+
"""
1378+
function first(itr)
1379+
x = iterate(itr)
1380+
x === nothing && return
1381+
return Some(x[1])
1382+
end
1383+
1384+
"""
1385+
Iterators.last(coll)
1386+
1387+
Return the last element of iterable `coll` wrapped in [`Some`](@ref).
1388+
1389+
If `coll` is empty, return `nothing`.
1390+
1391+
See also [`something`](@ref), [`Iterators.first`](@ref).
1392+
1393+
!!! compat "Julia 1.6"
1394+
This function was added in Julia 1.6.
1395+
1396+
# Extended help
1397+
1398+
This differs from [`last`](@ref) by not throwing an error for empty
1399+
iterables. It can be used with [`Iterators.filter`](@ref) to safely
1400+
get the last element of an iterable which matches a condition. With
1401+
[`last`](@ref), this use requires handling collections with no
1402+
elements matching the condition by catching the thrown
1403+
[`BoundsError`](@ref).
1404+
1405+
# Examples
1406+
```jldoctest
1407+
julia> Iterators.last(Iterators.filter(<(5), 1:10))
1408+
Some(4)
1409+
1410+
julia> isnothing(Iterators.last(Iterators.filter(isodd, 2:2:10)))
1411+
true
1412+
1413+
julia> something(Iterators.last(Iterators.filter(iseven, [5, 3, 4, 2, 6, 9])))
1414+
6
1415+
1416+
julia> something(Iterators.last(Iterators.filter(>(10), 1:10)), 0)
1417+
0
1418+
```
1419+
"""
1420+
last(itr) = first(reverse(itr))
1421+
13431422
end

doc/src/base/iterators.md

+2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ Base.Iterators.map
1919
Base.Iterators.filter
2020
Base.Iterators.accumulate
2121
Base.Iterators.reverse
22+
Base.Iterators.first
23+
Base.Iterators.last
2224
Base.Iterators.only
2325
Base.Iterators.peel
2426
```

test/abstractarray.jl

-25
Original file line numberDiff line numberDiff line change
@@ -1126,28 +1126,3 @@ end
11261126
end
11271127
end
11281128
end
1129-
1130-
@testset "first/last n elements of $(typeof(itr))" for itr in (collect(1:9),
1131-
[1 4 7; 2 5 8; 3 6 9],
1132-
ntuple(identity, 9))
1133-
@test first(itr, 6) == [itr[1:6]...]
1134-
@test first(itr, 25) == [itr[:]...]
1135-
@test first(itr, 25) !== itr
1136-
@test first(itr, 1) == [itr[1]]
1137-
@test_throws ArgumentError first(itr, -6)
1138-
@test last(itr, 6) == [itr[end-5:end]...]
1139-
@test last(itr, 25) == [itr[:]...]
1140-
@test last(itr, 25) !== itr
1141-
@test last(itr, 1) == [itr[end]]
1142-
@test_throws ArgumentError last(itr, -6)
1143-
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

test/iterators.jl

+12
Original file line numberDiff line numberDiff line change
@@ -848,3 +848,15 @@ end
848848
@test cumprod(x + 1 for x in 1:3) == [2, 6, 24]
849849
@test accumulate(+, (x^2 for x in 1:3); init=100) == [101, 105, 114]
850850
end
851+
852+
@testset "Iterators.first and Iterators.last" for itr in (1:9,
853+
collect(1:9),
854+
reshape(1:9, (3, 3)),
855+
ntuple(identity, 9))
856+
@test @inferred(Nothing, Iterators.first(itr)) == Some(1)
857+
@test @inferred(Nothing, Iterators.last(itr)) == Some(9)
858+
@test @inferred(Nothing, Iterators.first(Iterators.filter(>(5), itr))) == Some(6)
859+
@test @inferred(Nothing, Iterators.last(Iterators.filter(<(5), itr))) == Some(4)
860+
@test @inferred(Nothing, Iterators.first(Iterators.filter(>(9), itr))) === nothing
861+
@test @inferred(Nothing, Iterators.last(Iterators.filter(>(9), itr))) === nothing
862+
end

0 commit comments

Comments
 (0)