Skip to content

Commit e24e2f0

Browse files
giordanonalimilan
andauthored
Add two-argument first and last methods for any iterable (#34868)
* Add `{first,last}(::AbstractVector, ::Integer)` methods * Apply suggestions from code review Co-Authored-By: Milan Bouchet-Valat <[email protected]> * Add tests for OffsetArrays * Use `begin` in place of `firstindex` * Generalise two-argument `first` and `last` to any iterable Co-authored-by: Milan Bouchet-Valat <[email protected]>
1 parent a23a4ff commit e24e2f0

File tree

4 files changed

+86
-0
lines changed

4 files changed

+86
-0
lines changed

NEWS.md

+2
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ Standard library changes
6565
* `unique(f, itr; seen=Set{T}())` now allows you to declare the container type used for
6666
keeping track of values returned by `f` on elements of `itr` ([#36280]).
6767
* `Libdl` has been moved to `Base.Libc.Libdl`, however it is still accessible as an stdlib ([#35628]).
68+
* `first` and `last` functions now accept an integer as second argument to get that many
69+
leading or trailing elements of any iterable ([#34868]).
6870

6971
#### LinearAlgebra
7072
* New method `LinearAlgebra.issuccess(::CholeskyPivoted)` for checking whether pivoted Cholesky factorization was successful ([#36002]).

base/abstractarray.jl

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

346+
"""
347+
first(itr, n::Integer)
348+
349+
Get the first `n` elements of the iterable collection `itr`, or fewer elements if `v` is not
350+
long enough.
351+
352+
# Examples
353+
```jldoctest
354+
julia> first(["foo", "bar", "qux"], 2)
355+
2-element Vector{String}:
356+
"foo"
357+
"bar"
358+
359+
julia> first(1:6, 10)
360+
1:6
361+
362+
julia> first(Bool[], 1)
363+
Bool[]
364+
```
365+
"""
366+
first(itr, n::Integer) = collect(Iterators.take(itr, n))
367+
# Faster method for vectors
368+
function first(v::AbstractVector, n::Integer)
369+
n < 0 && throw(ArgumentError("Number of elements must be nonnegative"))
370+
@inbounds v[begin:min(begin + n - 1, end)]
371+
end
372+
346373
"""
347374
last(coll)
348375
@@ -361,6 +388,33 @@ julia> last([1; 2; 3; 4])
361388
"""
362389
last(a) = a[end]
363390

391+
"""
392+
last(itr, n::Integer)
393+
394+
Get the last `n` elements of the iterable collection `itr`, or fewer elements if `v` is not
395+
long enough.
396+
397+
# Examples
398+
```jldoctest
399+
julia> last(["foo", "bar", "qux"], 2)
400+
2-element Vector{String}:
401+
"bar"
402+
"qux"
403+
404+
julia> last(1:6, 10)
405+
1:6
406+
407+
julia> last(Float64[], 1)
408+
Float64[]
409+
```
410+
"""
411+
last(itr, n::Integer) = reverse!(collect(Iterators.take(Iterators.reverse(itr), n)))
412+
# Faster method for arrays
413+
function last(v::AbstractArray, n::Integer)
414+
n < 0 && throw(ArgumentError("Number of elements must be nonnegative"))
415+
@inbounds v[max(begin, end - n + 1):end]
416+
end
417+
364418
"""
365419
strides(A)
366420

test/abstractarray.jl

+15
Original file line numberDiff line numberDiff line change
@@ -1126,3 +1126,18 @@ 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

test/offsetarray.jl

+15
Original file line numberDiff line numberDiff line change
@@ -609,3 +609,18 @@ end
609609
@test_throws DimensionMismatch maximum!(fill(0, -4:-4, 7:7, -6:-5, 1:1), B)
610610
@test_throws DimensionMismatch minimum!(fill(0, -4:-4, 7:7, -6:-5, 1:1), B)
611611
end
612+
613+
@testset "first/last n elements of vector" begin
614+
v0 = rand(6)
615+
v = OffsetArray(v0, (-3,))
616+
@test_throws ArgumentError first(v, -2)
617+
@test first(v, 2) == v[begin:begin+1]
618+
@test first(v, 100) == v0
619+
@test first(v, 100) !== v
620+
@test first(v, 1) == [v[begin]]
621+
@test_throws ArgumentError last(v, -2)
622+
@test last(v, 2) == v[end-1:end]
623+
@test last(v, 100) == v0
624+
@test last(v, 100) !== v
625+
@test last(v, 1) == [v[end]]
626+
end

0 commit comments

Comments
 (0)